- web6047 - (2021/09/10(金) 現在、システム調整中のため、一部の表示がおかしいかもしれません)

[横幅 1024px以下]

web6047 2021年 12

プ ログラミングやRPG(作るほう)が好きな人の日記



個人的な趣味(プログラミング、イラスト、電子回路)のページです。

自身のパソコンのやりすぎに対する管理の表の公開、

最近見ているアニメや映画と 買い物の簡易なリスト、

それから日記を掲載しています。日記が主体です。


パソコン使用時間管理の公開:   パソコン使用時間管理

この表は、このウェブページの管理人のパソコンの使用時間を管理・制限するためのものです。





































NO PC WEEK に代わる PC 使用制限のしくみ(新β版)
No. A1.
開始時
運動
A2.
勉強
1問
A3.
終了時
運動
H1. 予定
作業内容
H2. 予定
作業詳細(進捗%)
判定×の理由
B. 実際
開始時刻
C. 予定
使用時間
(当日限度)
D. 予定
終了時刻
E. 実際
終了時刻
F. 実際
使用時間
G1. 判定
◎ 9分以下
○ 10分~19分
△ 20分~29分
× 30分以上
記録しなかった長時間作業あり
作業内容:Windows 不具合で再インストール
時間:半日
469 × PC-9801 アセンブラ学習
本「PC-9801スーパーテクニック」
P24「CPUの判別」
NekoProject2のバグを調査
12:00 1:30(unlock) 13:30 19:00 7:00 ×
記録しなかった長時間作業あり
作業内容:PC-9801 アセンブラ学習環境構築
時間:半日
468 × × ページ上のプログラムリスト表示機能の復帰(再度 作成) 挿絵について 19:35 1:30(1:30) 21:05 21:15 1:40
467 × ページ上のプログラムリスト表示機能の復帰(再度作成) 挿絵について 21:20 1:30(3:00) 22:50 23:00 1:40
466 × ページ上のプログラムリスト表示機能の復帰(再度作成) 挿絵の不具合対応 14:35 1:30(3:00) 16:05 16:26 1:51
465 ページ上のプログラムリスト表示機能の復帰(再度 作成) 行番号表示の不具合
目的の実現がもう少しだった
18:15 1:30(5:00) 19:45 20:18 2:03 ×
464 ページ上のプログラムリスト表示機能の復帰(再度 作成) 行番号表示の不具合
時間に甘く考えた
14:15 1:30(5:00) 15:45 16:49 2:34 ×
記録しなかった作業 複数あり
原因:「23時で電源オフ」により時間が少なくなる、と思い、開始時の運動や勉強を省略したくなり、記録も省略してしまったか ら。
463 × ×
ページ上のプログラムリスト表示機能の復帰(再度 作成) 関数部分挿絵機能 22:03 1:00(1:00) 23:03


記 録しなかった長時間作業あり
作業内容:ホームページ記事作成、グリフォンの記事に追記、ほか
時間:半日
462 ページ上のプログラムリスト表示機能の復帰(再度作成) 関数部分挿絵機能 20:00 1:30(5:00) 21:30


461 ページ上のプログラムリスト表示機能の復帰(再度作成) 関数部分挿絵機能 15:35 1:30(5:00) 17:05 17:07 1:32
記 録しなかった長時間作業あり
作業内容:グリフォンモデリング
時間:2,3時間程度
460 × × ページ上のプログラムリスト表示機能の復帰(再度作成) 関数部分挿絵機能
(電源タイマー導入し電源落ちたが、その後PC続けたぞ…! がっかりorz)
21:38 1:30(1:30) 23:08 23:27 1:49
(×)
459 × ホームページ更新 「マルチペイント」導入記事作成
気を抜いていた
19:25 1:30(1:30) 19:55 23:00 2:35 ×
458 × グリフォンモデリング 翼を青光りさせる 22:00 1:00(3:00) 23:00 23:17 1:17
457 ページ上のプログラムリスト表示機能の復帰(再度作成) 関数部分挿絵機能 14:35 2:00(3:00) 16:35 16:44 2:09
456 ページ上のプログラムリスト表示機能の復帰(再度 作成) 関数部分挿絵機能
進めたいと思い
15:50 1:30(4:00) 17:20 23:00 7:10 ×
455 ホームページ更新 11月の「パソコンに電源タイマー設置」の記事に 追記
時間をかけてしまった
12:15 0:30(4:00) 12:45 13:19 1:04 ×
454 ページ上のプログラムリスト表示機能の復帰(再度作成) その他 22:01 1:30(1:30) 23:31 23:33 1:32
記 録しなかった長時間作業あり
作業内容:「3Dお姉さんによるプログラム説明」復帰
時間:半日
記 録しなかった長時間作業あり
作業内容:H2タグを折るギミックの作成
時間:半日
453 ページ上のプログラムリスト表示機能の復帰(再度作成) プログラムの整頓 22:25 1:30(1:30) 23:55 24:12 1;47

この表の意図:

多くの人はパソコンのやりすぎやネットゲームのやりすぎには困っていると 思います。

参考に言うと、この表を使う前の私は 1 回の PC 使用時間がノンストップで 17 時間というときもあったし、平均で言うと毎日 9 時間はやっていたと思います。

この表を使ってパソコンの使用時間を 事前に決めてネッ ト上に公開 することで、パソコンのやりすぎを防止できたら、と思います。

また、数年前から考えてきましたが、そういう徹夜とか長時間作業をするよ りも、昼間の短時間作業のほうが生産性は高いのではないかと思います。そういう意味でも期待しています。

※以前は NO PC WEEK と称してパソコンを使用しない期間を設けることでやりすぎに対処してきましたが、もっと具合の良い方法はないかと考え、この表を使うようになりました。

記入の規則:

  • 日付は表示していません(私の生活パターンをすべて知らせるのはよくないから)。しかし、白と灰色の色分けは、同じ色の連続 で同じ日を表しています。
  • 左端の「A1. 運動」、「A2. 1問」、「A3. 運動」について
    この表の目的とは異なりますが、ついでとして、遊び100%の毎日を送るときでも勉強の習慣を忘れないために、たった1問で良い ので解くことにします。
    正直言うと毎回遊ぶ前に必ず1問勉強するのは心が折れそうです。でも慣れさえすれば…と思います。(追記:やってみると結構効果 的で役立っています)
    行ったら◎、行わなかったら× を記入します。
    2020年11月20日ぐらいから「A1. 運動」を追加しました。パソコンを行う前に運動することを強制するものです。腕立て伏せ10回とか腹筋10回とかです。
    (ホントだったら30回くらいはやりたいところですが、私は体の調子が悪いので、10回程度にしています)
    2021年6月26日「A3. 運動」を追加。PC使用の終了時にも運動する。
    A1はPC漬けによる筋力衰え対策の意味で、筋トレを行い、
    A3はPC作業でこった体をほぐす意味で、ラジオ体操やストレッチ体操を行う。
  • 右端の「G. 判定」について
    「D. 予定終了時刻」と「E. 実際終了時刻」を比べて、オーバーした時間によって判定を行います。
    ◎ 9分以下
    ○ 10分~19分
    △ 20分~29分
    × 30分以上 (オーバー理由を記載する。理由の統計を取れば何が問題なのか把握でき、改善しやすいです)
  • 平日の 1.5h、2.0h の PC 使用の連続が負担になっているので、週のスケジュールは下記のようにする。
    月水で PC 使用しないことでうまいぐあいに「体力回復」されて、「生活」がうまく回り、プログラミング以外の「創作活動」(イラスト等)に時間が取れることを期待します。
    月:0h
    火:1.5h
    水:0h
    木:1.5h
    金:1.5h
    土:5hまで(休日で、翌日も休日)
    日:3hまで(休日で、翌日平日) ←早起きなおかつ(日々の買い物とかじゃなく)散歩するなら 3h やって良い
    (※2021年2月2日:週の各時間を調整しました)
    (※2021年12月12日:電源タイマーで23時で終了することに期待できるので土日の各時間を1時間ずつ増やしました)
  • ×、△、"記録しなかった長時間作業" が多いと思ったら、バランスをとるために、NO PC WEEK※ を1週間実施する。
    NO PC WEEK とは私自身の健康のために私のパソコンの使用を制限する期間です)


ちなみに、分単位で記録を取ったりして、だいぶマメに見えるかもしれませんが、Windows の日本語入力(MS-IME)で「いま」と入力し、 変換 キーを押さずに ボタンを押すと現在の時刻になります。道具の便利さが人をマメに見せるのかもしれません。

例外事項:

  • 「E. 実際終了時刻」のあと、プログラミングの場合のみスッパリ終了しないで、今後のプログラミングの方針をテキストファイルに書くのは OK にしています。


「スーパーPC WEEK」:

連休中(3連休以上)に、NO PC WEEK をオフにして好きなだけパソコンを使ってよいとする期間を、「NO PC WEEK」に対して「スーパーPC WEEK」と言う。

ただし以下の決まりを守ること。

  • その日に自由にパソコンを使ってもよいが、1回あたり、いつものように表に記入すること。(開始時運動、勉強1問、終了時運 動も行うこと)
     
  • 1回ごとに、掃除、炊事、洗濯や、別の趣味など、まとまった作業を はさむこと。
     (理由: 1回1回がが連続してつながると回を分けている意味がなくなるから)
  • 24時に就寝するよう努めること。(強制ではないが、つとめること)
     
  • 連休の最終日は通常通りとする。
     
  • 1回の使用時間は2時間を最大とすること。

なお、表の中央やや右寄りの「C. 予定 使用時間(当日限度)」列の "当日限度" には UNLOCK と記入する。


中途結果:

結構いい結果になっています。 炊事や掃除、散歩、早起きなどが好ましいリズムでできるようになりました。

(2020年9月6日追記:散歩、早起きは最近あまりできていません。掃 除や炊事は理想的にできています)

ま た、パソコン以外の趣味も進むようになりました。電子回路、ガンプラ、RPG のプログラム以外のモンスターイラストやストーリーなど RPG の肉付け部分の創作、勉強、昔好きだったペーパークラフト等々。

そ してパソコンの趣味自体も深夜遅くまで行うよりも質が高くなったように感じます。制限された短い時間の中で結果を得ようとす るので、取捨選択が行われているし、時間が終了して、空いた時間ができ、それがほどよい休憩になり、今後の作業の方針を落ち 着いて検討することもできます。それが質につながっているのかなと思います。

この取り組みが、15 才 ~ 18 才くらいまでの高専(中退)に所属していた時に実施できていたら良かっただろうなと思います。でもそれくらいの年齢では経験が浅く、このような効果的なルール作りを行うこ とはできなかったと思います。自分は人に比べて「創作意欲」や「ゲームで遊ぶ欲望」におぼれやすいところがあり、そのコントロールは とても難しいです。

2021年10月23日追記:

ここで決めた規則通りにできていないこともありますが、それでも確実なメ リットがあるのでここに箇条書きで記します。

  • 事前事後に運動することで、身体の「こり」がある程度解消されるのを期待できる。
  • 規則正しい生活になっている。食事、掃除、ふろ等、以前は後回しにしていたものが、時間通りにできている。
  • 「平日月水はパソコン作業を行わない」という規則が、よいリズムになっている。平日毎日パソコンに向かっていたら、疲れもた まるだろうと思う。
  • 表中にどんな作業をしているのか記入しているので、訪問者に日ごろのパソコン作業の内容を、わりとリアルタイムに知らせるこ とが出来る。

他にもメリットはありますがたくさん書いてもしょうがないのでここまで。

また、反面、デメリットもあるので少し書いておきます。

  • 普通の人はやらないことを行い、なおかつWeb ページ上で公開しているので、「おかしな人だ」、「病気だろ」と思われてしまう。
  • 「創作は、生活のリズムを規則正しくしたほうが生産性が上がる」と考えていたが、ガツっと進めたいときに時間制限がかかり、 やきもきするし、実際、やりたいことが遅々として進まない。

デメリットはそれくらいしか思いつきません。我ながらメリットの多い表な んだと再確認しました。


自分の家族にすすめたい方へ:


(これは イラス トAC の無料素材です)

パソコンのやりすぎやネットゲームのやりすぎは社会問題にもなっているので、「うちの子についてなんとかしないと…」と思っている ご家族の方は多くいらっしゃると思います。

私の両親も過去に私について問題にしていました。学校へ行かず、毎日朝までパソコンに向かい、 悶々としていたんです。


この表はその家族が困っていたときから 30 年後に、私が自分で必要を感じて作ったものです。

私は今 一人暮らしをしていて、自分で生計を立てる中、パソコンにおぼれた生活をすると、生活がうまく回らなくなるんです。

具体的には、

  • 人前で疲れた顔を見せてしまい、人間関係がうまくいかなくなる。もっと具体的には、職場、とこや、お店のレジ、歯医者。
  • 掃除、洗濯、炊事が後回しになり、実質、それらを行う時間がなくなってしまう。深夜遅くや翌日に回したり、行わなかったりす る。

これらを改善するために表を作りました。


でも、このような必要にせまられて「自分の動機で始めた場合」と、「人からすすめられて始めた場合」とでは、結果が異なると思いま す。

自分の切実な動機で始めたなら自分から進んでこの表を活用すると思いますが、外から押し付けられたものはなかなか定着しないもので す。

あまり適当なことは言えませんが、「中途結果」タブの中の青い部分で 書いたことは、本人にとって得になることなので、「ときどき休憩して、他のあの趣味やってみたらどうだ?」とか「ときどき休憩したほ うがプログラミングの質が上がるって話だぞ?」という形ですすめてみたらどうでしょうか。(それでも最終的には自立してもらうことは 必要だと思いますが)


私が両親を困らせていたときに、突然、外へ一人で出て行って、一人暮らしを始めたり、接客業を始めたり、いくつか資格取得したりと いろいろ行えた理由というのは、正直言ってわかりません。(※しかし途中で失業して2度、実家に戻ったことがあります。1 回目は 21 才くらいのときに 5 年間、2 回目は 35 才くらいのときに1年未満、実家にいて、何もしてなかったり働いたりしていました)

私が両親を困らせていたのは 16 才 ~ 20 才くらいの学生のころですが、そのころ家族と私自身と友人たちがみんなそれぞれ、私の生活について心配したり困ったり悩んだり、あの手この手を試したりしていました。そう いう煮詰まったような状況が運命をそのように(解決の方向へ)動かすのかもしれません。運命がどうの というのは変ですが、そのくらいのことしか言えません。何かしら取り組む必要があるということですかね。


この社会問題はクリアーすべきものみたいです。



最近観ているアニメ: 最近観ているアニメ
日 付
(上ほど新しい)
タイトル 無料配信
配信日
公評価 私 評価
2021/12/26~視聴中 ジョジョの奇妙な冒険 スターダストクルセイダース(リンクは Yahoo GYAO で検索)
アクション・アドベンチャー/1989年週刊少年ジャンプ/各話24 分程度
4thシーズン「黄金の風」、3rdシーズン「ダイヤモンドは砕けな い」とGYAOで十分に楽しんで観てきましたが、2ndシーズン「スターダストクルセイダース」これもまた面白い!
OVA版(1993年)ではなくテレビアニメ版(2014年)。第48話まである。
観てみると、絵柄、演出、どれをとっても、なんともパワフルだと思う。
おすすめです!
月火・・土日 ★★★★
5
★★★★
4
2021/7/15~視聴中 ドラゴンボール(リンクは Yahoo GYAO で検索)
冒険・格闘技/1984年週刊少年ジャンプ/各話25分程度
月・水・金・・ ★★★★
4.8
★★★★
4.5
2021/5 /18~視聴中 はじめの一歩(リンクは Yahoo GYAO で検索)
ボクシング/1989年週刊少年マガジン/各話25分程度
大人になってわかる、ドラゴンボールを上回る面白さ。スケベ表現がた まにあるので男性向け?
10月16日(土) 追記
シリーズが「New Challenger」という新しいものに変わりました。
1話2話を見る限り、あまり面白くなくなってしまったかも。
でもその後、数話見ていると、以前ほどではないけど結構面白いです。
1月1日(土)追記
はじめの一歩 Rising #12 完全なるデンプシー破り
演出最高!感動で涙が出てきた。
悲しいとか嬉しいとかそういう涙ではなく、感動で身体がしめられてそれで出る涙。
「演出」が何なのか素人なので知らないけど、その時の音楽とか映像とか、その辺で感動させられました。
私に★5を出させるとは…やられたぞ!!
月・水・・土・ ★★★★
4.5
(第1シーズン)

★★★☆☆
↑たしかこれくらい
(New Challenger)

★★★☆☆
3.5
(Rising)
★★★★★
5.0
(第1シーズン)

★★★☆☆
3.0
(New Challenger)

★★★★☆
4.0
(Rising)
最近観た映画: 最近観た映画
日 付
(上ほど新しい)
タイトル 無料配信
(日付まで)
公評価 私 評価
2021/12/28(水) マトリックス【吹替版】(リンクは Yahoo! GYAO! で検索)
SFアクション/1999年アメリカ/2時間16分/キアヌ・リーブ ス
面白いんだけど、物語がもう一歩かなと思う。
2022/2/22
までGYAOにて無料配信
★★★★☆
4.2
★★★☆☆
3.9
2021/11 /27(土) コード211【吹替版】(リンクは Yahoo! GYAO! で検索)
刑事アクション/2018年アメリカ/1時間27分/ニコラス・ケイ ジ
面白かったのに、どうして公評価が★2.9なんだろ…
こういう言い方は変かもしれませんが、昭和の時代の全盛期のハリウッ ド映画が好きな方は楽しめるんじゃないかと思いました。
私が一番いいなと思ったのは、同乗者の少年かな。彼がいなかったらあ りきたりの映画になったかも。 オススメ!
公評価の低評価の具体的な内容は、だいたいは「面白そうな要素(傭兵 やインターポール)が多いのに、ひねりのない展開(正統的)で最後があっけない」でした。
いつも思いますが、公の評価はレベルが高く、少し厳しいのかなと思い ます。
無料配信
現在終了
★★☆☆☆
2.9
★★★★☆
4
2021/10 /25(月) バーニング・オーシャン【吹き替え版】(リンクは Yahoo! GYAO! で検索)
ディザスター/2016年アメリカ/1:47:14/マーク・ウォールバーグ(トランスフォーマー)、カート・ラッセル(バックドラフ ト)
2010年メキシコ湾原油流出事故」(リンクは Wikipedia)をもとに製作されたディザスタームービー。
技術的不手際により掘削中の海底油田から逆流してきた天然ガスが引火 爆発し、126人の作業員のうち11人が行方不明となり、17人が負傷した。
下記のアンダーウォーターと同じく、描写は完ぺき。なおかつ、面白 かった。
2021/11/5
までGYAOにて無料配信
★★★★☆
4
★★★☆☆
3.8

最近買ったもの: 最近買ったもの
日 付
(上ほど新しい)
タイトル 公評価 私 評価
2021/8/7 曲:アンインストール(リンクは Apple Music の当該ページへ)
2007年の「ぼくらの」というロボットアニメの主題歌
歌詞を読みましたが、「突然現れた新しい現実に対し、受け入れ、や るっきゃない」と言っているのかなぁ…
★★★★★
5
★★★★
4
2021/7/9 ゲーム:ザ・トリロジーズ -T&E SOFT / XTAL SOFT COLLECTION-(リ ンクは EGG の当該ページへ)
PCレトロゲームのセット。RPG開発の研究のために予約で購入。発売日は2022年2月だそうです。
発売前 発売前
2021/5/29 曲:最強○×計画(リンクは Apple Music の当該ページへ)
2006年の「すもももももも 地上最強のヨメ」というアニメの主題 歌
★★★★
4.5
★★★★
4


自己紹介: 自己紹介

47才、男、B型(BB)

電子機器の基板を製造する工場で、派遣で働いています。

プログラミングが好きで小学校5年生のころからずっと続けています。

アニメ見ます、ガンプラ作ります、映画見ます、ゲーム音楽いっぱい聴きます。

ページ上にていろいろ才能(少々 粗削りな才能)を発揮していると思いますが、なるべく自 分だけで終わらないようにといつも思っています。

いろいろ厳しい考え方も持っていますが、厳しすぎないように全体とのバランスも考えていま す。

将来の夢は5つくらい持っていますが、生きてる間に実現できそうにありません。

私が使っているペンネーム(ハンドルネーム)は新しい順に

ペ ンネーム(ハンドルネーム) いつごろ 場 所
daikei 現在(少) Yahoo Japan
d_kawakawa 現在(多) どこでも
cookiepuddingman、cookiepudingman ちょっと前 Twitter
かわ、kawa 30年前 パソコン通信

※基本的には d_kawakawa とお呼びください。

※なお、現在このホームページには私 への連絡手段がありません。気が向いたら用意します。


日記: 日記  (日記がこのサイトのメインコンテンツです。上の記事ほど新しい記事です)

2021/12/26(日)

PC-9801 の話題: 最近いろいろと本を買いました。

最近はレトロ PC の PC-9801UV11 をヤフーオークションで手に入れて、故障の修理をしたり、周辺機器とソフトウェア(MS-DOS や BASIC など)もオークションで購入し、すっかり落ち着いてきました。

現在は、PC-9801 の深いところをプログラミングで探っていこうとしているところです。

高校生のころ指をくわえて見ていた高い技術を、今こうやって ひもとくことで、新しい時代に通じるスキルアップになる――! と考えています。

(「新しい時代に通じるスキル」とは、「アセンブラを使う」とか、「LSI にアクセスする」、そして「昭和の時代の開拓者たちの意気揚々としたスピリッツを感じること


fig.
▲"有名すぎる解説書" と言われている

アスキー出版 PC-9801 スーパーテクニック (1992 年初版発行 定価 4,200 円)

30 年前のパソコンの NEC PC-9801 について、機械に直結したようなプログラム(VRAM アクセスなど)をインターネットで調べていると、高い確率でこの本が紹介されています。

この前購入した「98ハードに強くなる本II」はそろそろ読み終わりそうで、もうちょっと詳しい内容の本がほしいと思っていたことも あって、Amazon でこの本の中古本を購入しました。

Amazon 中古価格 2,800 円+送料無料 - 割引 280 円 - ポイント 59 円= 2,461 円

私の購入が最後だったらしく、Amazon の在庫がなくなってしまいました。

続けてこの本を手に入れるには、オークションの出品を待つか、Amazon などの通販サイトで中古本の入荷を待つかのどちらかになります。



fig.
▲当時の "絶好の人気書"

技術評論社 98マシン語 (1984 年初版発行 定価 2,900 円)

前に購入した「98ハードに強くなる本II」の「はじめに」のページで、

このように案内されており、実際、PC-9801 の機械語(アセンブリ言語)の文法についての情報が「98ハードに強くなる本II」と、それからインターネットにも少なかったので、Amazon で中古本を探して購入しました。

Amazon 中古価格 3,000 円+送料 350 円= 3,350 円

上図で示した案内の通り人気の本だったようです。元祖 PC-9801 が発売(1982 年)されて間もなくこの本「98マシン語」が発売され(1984 年)、その後、1989 年までの5年間に 14 刷も増刷されています。

その人気の秘密は…? 「98マシン語」の冒頭の記述によると、当時 16 ビットコンピュータや、マシン語の本は難しい本ばかりで、初心者にとっては難解な世界だったそうです。そこで、「予備知識が不要」で、「ソフトウェアは BASIC さえあればよい(BASIC の MON コマンドからアセンブリを入力)」という読者に親切な内容でマシン語の本を出そう!、と思ったんだそうです。当時の読者たちが必要としているものを提供したんですね。ど真 ん中ストライク。

ゲームソフトのドラゴンクエスト(ファミコン版)も難しい海外の RPG をどうにか日本の小学生たちにわかるように 親切な仕様で作ろうと考えて それでヒットしているので、『人々のニーズに親切な姿勢で応える』というのは どこの世界でも人気の秘訣みたいですね。



この PC-9801 の良書群は、できれば高校生の時に手に入れたかったです。

でも難しい内容が多いから、当時の私には理解は難しかっただろうな…。


2021/12/14(火)

PC-9801 の話題: お絵描きソフト「マルチペイント」(旧:ペイント)

ヤフーオークションで PC-9801 用(レトロPC)のお絵描きソフト「マルチペイント」を落札しました。

オークションは、100 円スタートで、気軽に、最高入札額 1,000 円で入札したところ、いつものように誰も入札せず、100 円のまま落札できました。送料 210 円なので、合計 310 円のお買い物でした。

Yahooオークション その商品ページ

すぐに郵便で届いて、念のためディスクイメージファイルを作っておこうと思って Windows のフロッピーディスクドライブで、読ませましたが、

読み込みエラー!!

やはり古すぎてカビているのか?

ダメかと思いましたが、3.5 インチディスクのシャッターを開いて中の磁性体表面のホコリなどを丁寧に取り除いて再度読ませたら、正常に読み込めました。カビてはいなかったようです。ヒヤヒヤ。310 円とはいえ期待していたので良かった。


動作確認は実機で行いましたが、この紹介記事のために 98 エミュレーターで動作させてスクリーンショットを撮りました。

▼インストール画面
▼起動時画面
▼メイン画面

「マルチペイント」は 30 年前に日本でとても有名だったお絵描きソフトです。

当時私も使っていて、kawa_001.mag ~ kawa_008.mag というファイル名で自作の絵をネットワーク(パソコン通信)で公開していました。

今では Windows のほうで、「PaintGraphic4 Pro」という優秀なペイントソフトがあるので、PC-9801 用のお絵描きソフトは必要ないんですが、安かったのと、懐かしさと、あとこのソフトが当時結構使いやすく、今でも使えるんじゃないか…と思って、ちょっと興味本位で導入し てみました。

昔のようにペイントソフトで地道に絵を描く時間はあるだろうか…


グリフォンモデリング

今のところこんな感じです。

もうちょっと自然に、ぼやぁっとさせたいんですが、試行錯誤しても方法がわからず。うーんいまいちだな…

笠倉出版のモンスター辞典では、グリフォンの色の説明を「翼は青か白」と言っているだけであって、「青光り」とは言っていません。

自作 RPG のモンスターイラストで使うだけならば、そんなに凝らなくていいんですよね。

次回は青光りをやめにして、正方形の枠の中におさまるようなポーズ(ゲームで使う画)を作って終了にしようと思います。

次のモンスターは「サキュバス」にするつもりです。

短時間で終わらせないと、いつまでたっても RPG は完成しないぞ……


…と言いつつ、やっぱり上の図が気に入らなかったので直しました。

直したけど、1つの画としての問題点は

  • 「透明な青い翼」は、完成されていなくて修正したほうがよい。
  • ブロックのようなハリボテのような画像だ。
  • 背景に とけこんでいなくて、浮いている。

それでもポーズや身体の構造など改善されて良くなっているところはあると思います。

その点について参考にしたい人のために、以下に説明を加えていこうと思います。


厳しさが良いものを生む

一度図を掲載して「気に入らない」と言って作り直しましたが、このように「違うと思ってやり直す」というのは、創作の基本だと思います。

漫画家が フラストレーションをつのらせて、クシャクシャポイするのはよくある話ですよね。

これでいいやと思ったらそれまでで、成長できません。

漫画家に対する編集者(漫画の可否を決める人)も「いいよ いいよ それでいいよ」と漫画家に逆らえずに何でも OK ばかり出していると、損をするのは漫画の読者なんですよね。

カラオケボックスのフードメニューの「チョコレートパフェ」も同じで、アルバイトさんが作ったパフェがちょっと残念な形をしているのに、店長が ストップをかけられないようでは、本当にかわいそうなのはそのパフェを受け取ったお客さんなんですよね。(お店で残念な形のパフェが出されるなん て、あまり聞かない話だと思いますが、低予算(お金がない会社)のカラオケボックスではそういうことがありえます)

厳しくないと良いものは作れません。


もっと良いものを作りたい。どうやったらもっと良くなるのか。その辺を考えて、面倒でも実行することが必要です。

しかし、ここで 抽象的なことをいろいろ言っても参考にならないと思うので、実際はどうなの?という具体的 なところを書いておきます。

今回、「面倒でも実行した」ところ:

  • 上半身に対して下半身が少し小さい気がしたので、下半身をひとまわり大きくした。(下図赤 部
  • 同様に、前足の手のひら(正確には手じゃないけど)も小さい気がしたのでひとまわり大きくした。(下図青 部
▼変更前
▼変更後(わずかに大きくした)


手のひらを大きくしたときに Shade3D のバグで、手のひらの細かいパーツの位置がバラバラになったり、手のひらのボーン(3Dモデルにポーズを付けるための内部骨格)がめちゃくちゃな方向を向いたりして、直す のにだいぶ手間がかかりました。

そんなふうに困難を経て、下半身を少し大きくして下図のように変化しました。

ほとんど同じに見えますが、よく見ると違いがわかります。

▼変更前(下半身が小さすぎないか?と感じる)
▼変更後(左図に比べてバランスが取れた)

おかしい、と思うところをできるだけ変えていく姿勢が必要なんです。


参考のために、具体的にどう発想されたのか

良いものを作るために他にやったこととして、

ライオンの下半身のポーズについては、頭の中にあったイメージを使いました。


▼右足をしっかりと後ろに伸ばし、左足は腰を落とすように曲げてライオンらしさを演出する

私の中にあったこのライオン・ポーズはたぶん、過去に見た、

そのへんから来ていると思います。

そこまでイラストの内訳を詳しく言う人はあまりいないと思いますが、具体的に話したほうが、実際のところがよくわかって参考になると思います。


”リアリティが重要なのだよ”

ところで、鳥ってパッと見で、胴体から足が直接出ているように見えますが、じつはあれ、しゃがんでいるんですよね。

 ←立っているように見えるけど、しゃがんで る。しかも つま先立ち。



ジョジョの奇妙な冒険の作者の方が「リアリティが重要なのだよ」と言っているように、足が胴体にくっついた状態で間違って描いてしまうよりは、 正確なしゃがみの骨格で描くほうが、「宙から舞い降りて獲物を とっつかまえる」というポーズが生きてくると思います。


2021/12/7(火)

プログラムリスト表示機能のテスト3

--- programlist.cgi ---
-help 引数が指定されました。
使用方法を表示します。

programlist.cgiはサーバープログラムから呼び出されるもので、^^com.programlist 引数^^ というコマンドで実行される。

【例文】
^^com.programlist /dir1/test.js -css:codeNormal -lang:js -indent:relative^^
^^com.programlist /dir1/test.js -css:codeRich -lang:js -indent:relative -title:タイトルです -explain:説明です^^
^^com.programlist -help^^
【動作制御スイッチ】
-css:(値)(値)は codeRich, codeNormal, codePlain。未指定時は codeNormal。
-cssSpecialblock:(値)(値)は type1のみ。未指定時は関数などをDIVでくくらない。
-lang:(値)(値)は js, html, txt のいずれか。未指定時はファイル名の拡張子。
-linenum:(値)(値)は true または false。未指定時は false。
-indent:(値) (値)は relative または absolute。未定時は relative。
relative
たとえばクラスやメソッドなどを(当スクリプトの内部処理として)DIVでくくる際に、メソッドはクラスからの相対的な位置関係でインデントされる。見た目は変わらずHTMLの構造が変わる。用途は入れ子であることを強調して表示したいときなど。
absolute
同様にDIVでくくる際に、メソッドは行頭からの位置関係でインデントされる。見た目は変わらずHTMLの構造が変わる。用途は関数と関数を横方向全体にわたる水平線で区切りたいときなど。
-id:(値)プログラムリストを表示するHTML要素のID属性を指定。未指定時は Perl の time 関数の返値(1970/1/1 00:00:00 UTC からの秒数)

【-css:codeRich 時にのみ有効なスイッチ】
-title:(値)(値)にタイトルを指定します。
-explain:(値)(値)に説明文を指定します。

【その他】
-helpこのヘルプを表示します。
-time処理にかかった時間を表示します。
-args指定された引数と、整形された引数を表示

【備考】
それぞれ、(値)に半角スペースを含ませたいときは(値)をダブルクオートでくくる。


2021/12/5(日)

「3Dお姉さんによるプログラム説明」復帰

システム移行にともない、動作が停止していた「3Dお姉さんによるプログラム説明」を復帰させました。

下記の画像リンクをクリックすると、その記事へ移動します。

リンク先の緑のボタンを押すと、プログラムの説明が始まります。


H2タグが折れる! ぶらぶら! ひゅーん

下に掲載している「プログラムリスト表示機能」の処理時間
ユーザ時間:0.023秒 / システム時間:0.000秒 / 子プロセスユーザ時間:0.000秒 / 子プロセスシステム時間:0.000秒
function h2buraburaSetting( argz ) {


//引数について

let pngURL = argz.pngURL; //ぶらぶら部分の画像

let partSxPer = argz.partSxPer; //ぶらぶら部分の位置Xを、H2タグ全体に対する割合で指定

let partSyPer = argz.partSyPer; //ぶらぶら部分の位置Yを、H2タグ全体に対する割合で指定

let partWidthPer = argz.partWidthPer; //ぶらぶら部分の幅を、H2タグ全体に対する割合で指定

let partHeightPer = argz.partHeightPer; //ぶらぶら部分の高さを、H2タグ全体に対する割合で指定

let originXPer = argz.originXPer; //回転の中心位置Xを、ぶらぶら部分の幅に対する割合で指定

let originYPer = argz.originYPer; //回転の中心位置Yを、ぶらぶら部分の高さに対する割合で指定

let areaPer1 = argz.areaPer1; //画面上の検知領域の開始高さを、画面の高さに対する割合で指定

let areaPer2 = argz.areaPer2; //画面上の検知領域の終了高さを、画面の高さに対する割合で指定

//※画面上の検知領域にH2タグが入ると、アニメを開始する

let tweakX = argz.tweakX; //画像と画像の境目をなるべく消すための位置調整X。単位はpx

let tweakY = argz.tweakY; //画像と画像の境目をなるべく消すための位置調整Y。単位はpx




let h2s = document.getElementsByTagName( "h2" );

let parts = new Array();

let props = new Array();

let frame;

let running = false;


//スクロールして、H2タグが画面の特定位置に入ったかどうかを調べる部分

let scrollx = function() {

let numberOfInside = 0;

for( let i = 0; i < h2s.length; i++ ) {

//各H2タグについて


let h2 = h2s[ i ];

let rect = h2.getBoundingClientRect();

let centerY = rect.top + rect.height / 2;


if( centerY > window.innerHeight * areaPer1 &&

centerY < window.innerHeight * areaPer2 ) {

// console.log( "inside", h2.innerText );

props[ i ].inside = true;

props[ i ].startFlg = true;

numberOfInside ++;

} else {

// console.log( "outside", h2.innerText );

props[ i ].inside = false;

}

}

//check. 1つでも画面の特定位置に入ったら、アニメframeを開始

if( numberOfInside ) {

if( ! running ) {

running = true;

console.log( "frame start." );

frame( 0 );

}

}

}

addEventListener( "scroll", scrollx );


//初期状態 セッティング

for( let h2 of h2s ) {

//各H2タグについて


//ぶらぶらする部分

let part = document.createElement( "img" );

part.style.position = "absolute";

part.style.zIndex = 255;

part.src = pngURL;


//H2タグ

h2.style.position = "relative";

h2.appendChild( part );


parts.push( part );


//アニメ制御の変数群(H2タグごとに用意)

props.push( {

nowAngle : 0,

angleDir : 1,

sequence : 0,

graphX : 0,

graphXDir : 1,

rate : 0.079, //放物線の係数

top : 0,

startFlg : 0,

endFlg : 0,

} );

}


//ぶらぶらする部分の位置 セッティング(ウィンドウリサイズ時の再設定兼用)

let resizex = function() {

for( let i = 0; i < h2s.length; i++ ) {

let h2 = h2s[ i ];

let part = parts[ i ];

let h2rect = h2.getBoundingClientRect();

part.style.left = h2rect.width * partSxPer + tweakX + "px"; //H2タグの60%の位置。0.5は0.5ドット

props[ i ].top = h2rect.height * partSyPer + tweakY;

part.style.top = props[ i ].top + "px"; //しがみつく人の分、上へ

part.style.width = h2rect.width * partWidthPer + "px"; //H2タグの40%の幅

part.style.height = h2rect.height * partHeightPer + "px"; //H2タグの(addSize*100)%の高さ

part.style.transformOrigin =

( h2rect.width * partWidthPer * originXPer ) + "px " +

( h2rect.height * partHeightPer * originYPer ) + "px";

//partの現在の位置から、H2タグの幅の20%分右、

//partの現在の位置から、H2タグの高さの(addSize*100)%分下が

//ぶらぶら回転の中心

}

}

addEventListener( "resize", resizex );



//アニメ・フレーム(requestAnimationFrameを使用)

let metronome = 0;

let bktm = 0;

frame = function( tm ) {


metronome ++;

let endcount = 0;


//70msごとに処理する

if( tm - bktm >= 70 ) {

bktm = tm;

metronome = 0;


let everyThingStopped = true;

for( let i = 0; i < h2s.length; i++ ) {

//各H2タグについて


let prop = props[ i ];

//check. アニメ終了したH2タグは処理しない

if( prop.endFlg ) {

endcount ++;

continue;

}

//check. まだアニメ開始していないH2タグも処理しない。

if( ! prop.startFlg ) continue;



//1つでも処理したフラグ

everyThingStopped = false;


let part = parts[ i ];

let graphY; //方眼紙的なY座標


//アニメの各シーン

switch( prop.sequence ) {

//H2タグが折れ始めるシーン

case 0:

graphY = prop.graphX * prop.graphX * prop.rate; //放物線

prop.graphX += prop.graphXDir;

prop.nowAngle += graphY;

//check.

if( prop.graphX > 14 ) { //放物線の途中でおりかえす

//speed max

prop.graphXDir *= -1;

} else if( prop.graphX == 0 ) { //左端まで振ったら次のシーンへ

//speed min (left side)

prop.sequence ++;

prop.graphXDir *= -1;

prop.rate -= 0.01;

}


break;

//ぶらぶらするシーン

case 1:

graphY = prop.graphX * prop.graphX * prop.rate;

prop.graphX += prop.graphXDir;

prop.nowAngle -= graphY * prop.angleDir;

//check.

if( prop.graphX > 13 ) {

//speed max

prop.graphXDir *= -1;

} else if( prop.graphX == 0 ) {

//speed min

prop.graphXDir *= -1;

prop.angleDir *= -1;

prop.rate -= 0.02; //ぶらぶらがだんだん小さくなる

//check.

if( prop.rate < 0 ) { //停止したら次のシーンへ

prop.sequence ++;

prop.graphX = 0;

}

}

break;

//落ちるシーン

case 2:

prop.graphX += 3;

prop.top = prop.graphX * prop.graphX * 0.5;

//check. ある程度落ちたら消して終了

if( prop.top > window.innerHeight ) {

part.style.display = "none";

prop.endFlg = 1;

}


break;

}//switch


//以上で計算した回転角度を適用する。(case 0,1)

part.style.transform = "rotate(" + prop.nowAngle + "deg)";

//以上で計算した落下位置を適用する。(case 2)

part.style.top = prop.top + "px";

}//for


//check. すべてのH2タグがアニメを終了した

if( endcount == h2s.length ) {

console.log( "frame end." );

removeEventListener( "resize", resizex );

removeEventListener( "scroll", scrollx );

return;

}

//check. アニメ中のものが1つもない

if( everyThingStopped ) {

console.log( "frame suspend" );

running = false;

return;

}

}//if



requestAnimationFrame( frame );

}//frame


//イベントハンドラだが、手動で実行する

resizex(); //ぶらぶらする部分の位置決め

scrollx(); //スクロールしてない時点で、すでに画面の特定位置に入ってるか


}//h2buraburaSetting


サ ンプルを別ウィンドウで実行  h2buraburaSetting() の引数について図解


2021/12/4(土)

プログラムリスト表示機能のテスト2

下に掲載している「プログラムリスト表示機能」の処理時間
ユーザ時間:0.289秒 / システム時間:0.008秒 / 子プロセスユーザ時間:0.000秒 / 子プロセスシステム時間:0.000秒
 ← 押すと切り替えます RPGのプログラム
私が開発しているRPGのプログラムです
(未完成です)
/* memo.

名前のすみわけ(理想であって、実際はそうなっていないかもしれない)

"要素"

配列の要素 element

画面の要素 element → craft

"イベント"

イベントドリブンのイベント イベント という

シナリオスクリプトのイベント ストーリー という


"ストーリー"

以下のような、その場所でどんな行動をとったらこの関数、という構造のオブジェクトをstoryと呼ぶ。

{

"walk" : function() {...},

"outer.goton" : function() {...},

}

storyの配列をstoriesと呼ぶ。

storyを構成する story.walk や story[ "outer,goton" ] をshortStoryと呼ぶ。


"アイテム"

もちもの トレジャー

メニュー項目 アイテム


キャラの状態

直近の状態 status 毒を受けたとか

恒常的な状態 property 力がどれだけあるとか


座標

グラフィクス gx, gy, gw, gh

マス目のある画面 col, row, cols, rows

データ上の位置 x, y, w, h

マス目 1マスの名称 座標

画面のマス目 tile style.gridCol,style.gridRow

要素内に作成したマス目 inner cell col,row


配列変数の名前と、同じデータを持つ連想配列変数の名前

配列 -s たとえばcharacters

連想配列 -z たとえばcharacterz


そのとき、たとえばsprites、spritezの読み方について

配列時 sprites スプライトス

連想配列時 spritez スプライトス

※どちらも-スと読む。つまり-zは見た目の区別でしかない


また、childの複数形childrenについて -z を表現できないので、

childs, childz とする。

連想配列のkeyとキー入力のkey

連想配列 name

キー入力 key

言葉の意味

tweak 調整する

strategy 広い視野で物事を見て全体の方向性を決めていく

tactic 実務レベルでどのように対処するのかという部分的な一つの計画


async関数とawait


awaitはasync関数を暗黙的に分割します。


async test() {

処理1

await 関数など

処理2

}

処理1までは通常の関数と同じように動作し、

awaitの後の処理2はawaitの行が終わる(= Promiseの解決が履行される)までは実行されません。

また、処理2が終わった後は、test関数が呼び出しされた場所には戻りません。

test関数の最初のawaitの時点で、プログラムは2つ(並列)に枝分かれします。

片方はtest()を出てtest()を呼び出した次の行へ進み、もう片方はawait以降を実行します。

await以降を実行した後は、どこにも戻らず、終了します。

このRPGのプログラムにおいては、枝分かれすることはあまり価値がありません。

1つの関数を途中で止めることができるawaitに価値があります。

*/


/*

----- ----- -- --

12345 12345 12 12

45 3 2\n


----- ----- -- --

12345 12345 12 12

45 3 2

*/


let str = 'Twas\' the night before Xmas...';

let str = "Twas\" the night before Xmas...";

let newstr = str.replace(/xmas/i, 'Christmas');

console.log(newstr); // Twas the night before Christmas...

let re = /(\w+)\s(\w+)/;

let str = 'John Smith';

let newstr = str.replace(re, '$2, $1');

console.log(newstr); // Smith, John

function f2c(x) {

function convert(str, p1, offset, s) {

return ((p1 - 32) * 5/9) + 'C';

}

let s = String(x);

let test = /(-?\d+(?:\.\d*)?)F\b/g;

return s.replace(test, convert);

}



text = "a;b;c";

/*

abc = /DEF/ig;

*/

text.split( /;/ );

re = /[0-9]+/;

re = /DEF/gi;

reg = /[a-z]+/i;

var re = /123/g;

var str = 'abc123def';

re.test(str);

var re = /123/g;

re.test("abc123def");

var str = "2019年12月31日";

var result = str.match(/(?<year>\d+)年(?<month>\d+)月(?<day>\d+)日/u);

console.log(result.groups.year); // 2019

console.log(result.groups.month); // 12

console.log(result.groups.day); // 31

console.log(RegExp.input); // => "abc123def"

re.test("abc456def"); // マッチしないので index は変化しない

console.log(RegExp.input); // => "abc123def"

"12:34:56".match(/(\d+):(\d+):(\d+)/);

console.log(RegExp.$1); // => 12

console.log(RegExp.$2); // => 34

console.log(RegExp.$3); // => 56

console.log(re.lastIndex); // => 6

"abc123def".match(/(123)/);

console.log(RegExp.lastMatch); // => 123

console.log(RegExp.leftContext); // => abc

console.log(RegExp.rightContext); // => def

console.log(RegExp.lastParen); // => 123

var re = /A[0-9]+/g;

"abc".match(/ABC/) // マッチしない

"abc".match(/ABC/i) // マッチする;

"123\n456\n789".match(/^456/) // マッチしない

"123\n456\n789".match(/^456/m) // マッチする

res = "12:34:56".match(/\d+/g);

"🍔".match(/^.$/) // 2文字とみなすのでマッチしない

"🍔".match(/^.$/u) // マッチする

reg = /A/y;

for (var m of "2020-12-31".matchAll(/[0-9]+/g)) {

console.log(m);

}

if( text.match( /(.*");(.*);(.*)/ ) ) {

alert( RegExp.$1 + ", " + RegExp.$2 + ", " + RegExp.$3 );

}


// |

// V

//---■■■ 1 Utl ■■■

class Utl {//c

static arrayForward( array, element ) {//m

//配列内の要素を末尾に置く


let idx = array.indexOf( element );

//check.

if( idx == -1 ) {

alert( "Utl.arrayForward() 失敗した" );

return;

}


array.splice( idx, 1 );

array.push( element );

}

static accessBy( /**/ ) {//m

//test.test2.test3のようなアクセスで、test2がnullだとエラーで止まる。

//accessBy( test, "test2", "test3" )とすれば止まらず、nullを返す。

//try,catchもあるが、なじみがないので。

let array = Array.from( arguments );

let object = array.shift();

do {

if( object === null ) return null;

if( object === undefined ) return undefined;

object = object[ array.shift() ];

} while( array.length );

return object;

}

static array2object( array, object, keyMaker ) {//m

//配列を連想配列に変換する。その際の名前はkeyMakerが作る

//objectは作成済みの場合

for( let element of array ) {

object[ keyMaker( element ) ] = element;

}

}


static objectFrom( array, keyMaker ) {//m

//配列を連想配列に変換する。その際の名前はkeyMakerが作る

//objectは作成済みではない場合

let object = new Object();

for( let element of array ) {

object[ keyMaker( element ) ] = element;

}

return object;

}

static lengthBin( str ) {//m

//漢字を2バイト、半角を1バイトとしてバイト数を計算 (UTF8を前提)

return encodeURI( str ).replace( /%20/, "*" ).replace( /%..%..%../g, "**" ).length;

}

static lengthBinDiv2( str ) {//m

//lengthBinの結果を2で割り、小数点切り上げ

return Math.ceil( Utl.lengthBin( str ) / 2 );

}

static maxLength( strArray ) {//m

//文字列配列中で、一番長い文字列の文字数を返す

let v = 0;

for( let str of strArray ) v = Math.max( v, String( str ).length );

return v;

}

static maxLengthBin( strArray ) {//m

//そのバイト版

let v = 0;

for( let str of strArray ) {

v = Math.max( v, Utl.lengthBin( String( str ) ) );

}

return v;

}

static maxLengthBinDiv2( strArray ) {//m

//その、2で割り、小数点切り上げ版

return Math.ceil( Utl.maxLengthBin( strArray ) / 2 );

}

static copyObjectKai( object, history ) {//m

//オブジェクトを厳密にコピーするの改造版

//check. 最初の呼び出し

if( history == undefined ) {

history = {

origin : new Array(),

copy : new Array(),

}

}

if( object instanceof Object ) {

//check. すでにコピーしたことがある(無限ループ検知)

if( history.origin.includes( object ) ) {

let idx = history.origin.indexOf( object );

return history.copy[ idx ];

}

history.origin.push( object );

let copy;

if( object instanceof Array ) {

copy = new Array();

history.copy.push( copy );

for( let i = 0; i < object.length; i++ ) {

copy[ i ] = Utl.copyObjectKai( object[ i ], history );

}

} else {

copy = new Object();

history.copy.push( copy );

for( let name in object ) {

//改造箇所はここだけ

//オブジェクトのメンバでcopy:trueを持つ場合に限り

//厳密にコピーをする。

//copy:trueがないと、コピー元のオブジェクトの参照になる。

//モンスターやトレジャーはカタログをコピーするが

//HPや使用回数だけはカタログへの参照ではなくコピーにしたい。

if( object[ name ].copy ) {

//HPや使用回数はこちら

copy[ name ] = Utl.copyObjectKai( object[ name ], history );

} else {

//その他定義は読むだけなので参照でよい

copy[ name ] = object[ name ];

}

}

}

return copy;

} else {

return object;

}

}

}//Utl

//---■■■ 2.5 Drawing ■■■


class Drawing {//c

constructor() {//m

this.x = 0;

this.y = 0;

this.w = 16;

this.h = 16;

this.afterEffects = new Array();

this.beforeEffects = new Array();

}

effectDraw1( cc ) {//m

cc.save();

// cc.fillStyle = this.style.backgroundColor;

// cc.fillRect( 0, 0, this.w, this.h );


cc.translate( this.centerX, this.centerY );


for( let i = 0; i < this.beforeEffects.length; i++ ) {

let effect = this.beforeEffects[ i ];

if ( effect.call( this, cc ) ) this.beforeEffects.splice( i--, 1 );

}

cc.translate( -this.centerX, -this.centerY );

}

effectDraw2( cc ) {//m

for( let i = 0; i < this.afterEffects.length; i++ ) {

let effect = this.afterEffects[ i ];

if ( effect.call( this, cc ) ) this.afterEffects.splice( i--, 1 );

}

cc.restore();

}

draw( cc ) {//m


}

}


//---■■■ 2 Craft ■■■

/*

Craft を extends しているもの

class App

class MessageWindow

class List

class Menu extends List

class TitleScreen

class MapScreen

class BattleView


*/

class Craft extends Drawing {//c

constructor( style, app ) {

super();

this.app = app ? app : this;

//check.

if( this == app && style.gridCellSize == undefined )

alert( "appに限り、styleにgridCellSizeの定義が必要です。" );


//デフォルト値と上書き

this.style = {

gridCol : 0,

gridRow : 0,

gridCols : 5,

gridRows : 5,

backgroundColor : "black",

};

this.assignStyle( style );


this.name = this.constructor.name;

this.childs = new Array();

this.prevFocus = null;


}

assignStyle( newStyle ) {

Object.assign( this.style, newStyle );

this.x = this.style.gridCol * this.app.style.gridCellSize;

this.y = this.style.gridRow * this.app.style.gridCellSize;

this.w = this.style.gridCols * this.app.style.gridCellSize;

this.h = this.style.gridRows * this.app.style.gridCellSize;

this.centerX = this.w / 2;

this.centerY = this.h / 2;

}

typeKey( key ) {//m

}

senseKey( key ) {//m

}


draw( cc ) {//m

this.drawWindow( cc );

this.drawChildren( cc );

}

drawWindow( cc ) {//m


//ウィンドウ

let x = - this.app.style.gridCellSize / 2;

let y = - this.app.style.gridCellSize / 2;

let w = this.w + this.app.style.gridCellSize;

let h = this.h + this.app.style.gridCellSize;


cc.fillStyle = "black";

cc.fillRect( x, y, w, h );


//ウィンドウ 面

cc.fillStyle = "white";

cc.fillRect( x, y, w, h );


//ウィンドウ 枠

if( this.app.currentCraft == this ) {

cc.strokeStyle = "yellow";

cc.strokeRect( x + 1, y + 1, w - 2, h - 2 );

}

cc.strokeStyle = "black";

cc.strokeRect( x, y, w, h );

}

drawChildren( cc ) {//m

for( let child of this.childs ) {

cc.save();

cc.translate( child.x, child.y );

child.draw( cc );

cc.restore();

}

}

searchStory() {//m

}

focus() {//m

//check. すでにフォーカスされている。

if( this.app.currentCraft == this ) {

console.log( "already", this.name, "focused" );

return;

}


if( this.parent )

console.log( "focus", this.name, "at", this.parent.name );

else

console.log( "focus", this.name );


if( this.prevFocus == undefined )

this.prevFocus = this.app.currentCraft;

this.app.currentCraft = this;


//前面に移動

if( this.parent ) {

let idx = this.parent.childs.indexOf( this );

this.parent.childs.splice( idx, 1 );

this.parent.childs.push( this );

}

}

appendChild( child, nofocus ) {//m

this.childs.push( child );

child.setParent( this );

if( ! nofocus ) child.focus();

return child;

}

appendFloat( child ) {

this.appendChild( child, true );

}

setParent( parent ) {

this.parent = parent;

//check.

for( let name in this.style ) {

if( this.style[ name ] == "inherit" ) {

this.style[ name ] = parent.style[ name ];

}

}

}

removeChild( child ) {//m

console.log( "remove", child.name, "from", this.name );

let idx;


if( ( idx = this.childs.indexOf( child ) ) > -1 ) {

this.childs.splice( idx, 1 );

if( child.prevFocus ) child.prevFocus.focus();

} else {

alert( "警告:正体不明のオブジェクトをcraft.removeChild()しようとしました." );

}


return child;

}

removeAllCrafts() {//m

for( let child of this.childs ) {

child.removeAllCrafts();

}

this.parent.removeChild( this );

}


}//Craft

// |

// V

//---▼▼▼ 3 App ▼▼▼

class App extends Craft {//c

constructor( canvasId ) {

//初期化が3つに分かれているのはなぜか:

//this.start() は止めたり再開したりするためのもの。

//this.initialize() は 停止と再開を考慮しなくてもよい静的な初期化を行う。

//また、this.initialize() は非同期処理 async ができる。

//(this.constructor() は非同期処理ができない)

//そういうわけで初期化が、constructor, initialize, start と3つに分かれている。


let canvas = document.getElementById( canvasId );

let cc = canvas.getContext( "2d", { alpha : false } );


window.LA = new LogicAnalyzer( cc, {

titleFontSize : 5,

commentFontSize : 5,

horizontalDiv : 16,

trigChart : "'exit' story",

horizontalShift : 132,

}, true );


let logiczoom = 1;

let canvasWidth = 256 / logiczoom; //プログラム上の論理的サイズ

let canvasHeight = 224 / logiczoom;

canvas.width = canvasWidth;

canvas.height = canvasHeight;

if( 0 ) canvas.style.imageRendering = "crisp-edges"; //アンチエイリアス


/* 解像度変更機能:


mozaic canvasWidth canvas.width scale

0.5 256 2048 8 さらにきめ細かい

1 256 1024 4 きめ細かい(標準)

2 256 512 2 少し荒い

4 256 256 1 ファミコン風

*/

if( 1 ) {

let mozaic = 1; //見た目の粒の大きさ

canvas.width = canvasWidth / mozaic * 4; //物理的

canvas.height = canvasHeight / mozaic * 4;

cc.scale( 4 / mozaic, 4 / mozaic ); //物理1ピクセル当たりの論理ピクセル数

}


let gridCellSize = canvasWidth / 32;


super( {

gridsPerInnerCell : 2, //内部1セルあたり、何gridか。

//gridはアプリ内での絶対的なマス目。

//例えば、gridが8ピクセルで、マップチップが16ピクセルなら、チップ当たり2grid

//という意味。

gridCol : 0,

gridRow : 0,

gridCols : Math.floor( canvasWidth / gridCellSize ),

gridRows : Math.floor( canvasHeight / gridCellSize ),


gridCellSize : gridCellSize, //全画面共通の最小単位。

innerCellSize : gridCellSize * 2, //その画面の単位。マップチップ等

}, null );


this.cc = cc;

this.cc.canvasWidth = canvasWidth;

this.cc.canvasHeight = canvasHeight;


this.effectz = new Effectz( this );

}

// |

// V

async initialize() {//m

//初期化が3つに分かれているのはなぜか:

//this.start() は止めたり再開したりするためのもの。

//this.initialize() は 停止と再開を考慮しなくてもよい静的な初期化を行う。

//また、this.initialize() は非同期処理 async ができる。

//(this.constructor() は非同期処理ができない)

//そういうわけで初期化が、constructor, initialize, start と3つに分かれている。


this.message = null;


//---メトロノーム

this.metronomez = new Object();


this.addMetronome( "t1000_i2" );

this.addMetronome( "t500_i2" );

this.addMetronome( "t250_i2" );

//カーソル専用(手動追加)

this.metronomez.forCursor = {

maxTime : 500,

maxIndex : 0,

time : 0,

index : 0,

toggle : true,

changed : false,

}

/*

メトロノームの各フラグの変化タイミング図解(タイミングチャート)

maxTime : 250,

maxIndex : 3, の場合の各フラグの動き↓


(経過時間) 0 ... 250 ... 500 ... 750 ... 1000 ...(ms)


time : 0, 0 ..249 0 ..249 0 ..249 0 ..249 0 .. ...(ms)


index : 0, 000000001111111122222222000000001111 ...


toggle : true, ~~~~~~~~________~~~~~~~~________~~~~ ...


changed : false, ________~_______~_______~_______~___ ...


~ : true

_ : false

... : 略

※このグラフ中の1文字単位の縦1列はrequestAnimationFrameに相当する


つまり、requestAnimationFrameごとに、

timeは経過時間を増やしていきmaxTimeになると0に戻る

indexはtimeがmaxTimeになるごとに1増え、maxIndexになると0に戻る

toggleはtimeがmaxTimeになるごとに真偽を反転する

changedはtimeがmaxTimeになったときだけ真になる


maxTimeでアニメのパラパラのスピード決め

toggleやchangedでアニメのパラパラの実行を判断

indexでアニメのパラパラの画の切り替え

*/


this.spritez = {

player : {

images : [ "←", "-", "↑", "|", "→", "-", "↓", "|" ],

metronome : this.metronomez.t500_i2,

},

}


this.artz = {

spritez : this.spritez,

bgms : new Object(),

soundz : new Object(),

imagez : new Object(),

}

//---素材のプリロード

let dir = location.pathname.replace( /%20/g, " " ).replace( /\/[a-z0-9_ .-]*$/i, "" ) + "/";


this.cc.fillStyle = "cyan";

let str = "want a moment"

this.cc.fillText( str, ( this.cc.canvasWidth - this.cc.measureText( str ).width ) / 2, 100 );


await Promise.all( [

this.load( "bgm", "field", dir + "_bgm/街/MusMus-CT-NV-24/MusMus-CT-NV-24.mp3" ),

this.load( "bgm", "town", dir + "_bgm/街/MusMus-BGM-078/MusMus-BGM-078.mp3" ),

this.load( "sound", "pushA", dir + "_sound/pushA.mp3" ),

this.load( "sound", "pushWall", dir + "_sound/pushWall.mp3" ),

this.load( "sound", "walk", dir + "_sound/walk.wav" ),

this.load( "sound", "damage", dir + "_sound/musmus_other_set/othr10.mp3" ),

this.load( "image", "slime", dir + "_img/slime.png" ),

this.load( "image", "battleBG", dir + "_img/battle.png" ),

] );

console.log( "素材のロード完了." );


this.monsters = new Array(); //戦闘中のモンスター

this.characters = new Array();

this.characterz = new Object();

this.treasureCatalog = new Object();


//---■メニュー

this.menusrcs = [

{

name : "yesno",

title : "",

items : [

{

name : "はい",

},

{

name : "いいえ",

}

],

},

{

name : "camp",

title : "コマンド?",

argzName : "cmd",

items : [

{

name : "どうぐ",

title : "だれの?",

items : this.characters,

argzName : "char1",

assistMenu : {

title : "どれ?",

argzName : "dougu",

row : 1, //だれでも表示高さを固定して表示

},

},

{

name : "しらべる",

},

{

name : "まほう",

title : "だれの?",

items : this.characters,

argzName : "char1",

},

],

},

//---

{

name : "つかう",

title : "だれに?",

items : this.characters,

argzName : "char2",

story : async function( argz ) {

this.openMessage();

await this.message.write( argz.char1.name + "は" + argz.char2.name + "に" + argz.dougu.name + "を使った!#k" );

let lostFlg = true;

if( argz.dougu.property.limit != undefined ) {

//limitが0になったらなくなるタイプ

argz.dougu.property.limit --;

//check.

lostFlg = argz.dougu.property.limit == 0;

if( lostFlg ) {

this.message.clear();

await this.message.write( "なんと " + argz.dougu.name + " は壊れてしまった!#k" );

}

}

if( lostFlg ) {

let idx = argz.char1[ argz.cmd.name ].indexOf( argz.dougu );

argz.char1[ argz.cmd.name ].splice( idx, 1 );

}

this.closeMessage();

},

},

{

name : "わたす",

title : "だれに?",

items : this.characters,

argzName : "char2",

story : async function( cmd, char1, dougu, cmd2, char2 ) {

this.openMessage();

await this.message.write( char1.name + "は" + char2.name + "に" + dougu.name + "を渡した!#k" );

this.closeMessage();

},

},

{

name : "すてる",

story : async function( cmd, char1, dougu, cmd2 ) {

this.openMessage();

await this.message.write( char1.name + "は" + dougu.name + "を捨てた!#k" );

this.closeMessage();

},

},

]

this.menusrcz = Utl.objectFrom( this.menusrcs, element => element.name );

//---■トレジャー

this.treasureCatalog = {

"どうけん" : {


},

"せいどうけん" : {


},

"てっけん" : {


},

"こうてつけん" : {


},

"ひほう" : {

property : {

copy : true,

limit : 3,

},

assistMenuCamp : {

title : "どうする?",

argzName : "dousuru",

items : [

this.menusrcz[ "つかう" ],

this.menusrcz[ "わたす" ],

this.menusrcz[ "すてる" ],

],//items

},

},

"なんこう" : {

property : {

copy : true,

},

assistMenuCamp : {

title : "どうする?",

argzName : "dousuru",

items : [

this.menusrcz[ "つかう" ],

this.menusrcz[ "わたす" ],

this.menusrcz[ "すてる" ],

],//items

},//nextMenu

assistMenuBattle : this.menusrcz[ "つかう" ],

},//なんこう

}//treasureCatalog


//tweak. キーを名前として要素内に登録

for( let name in this.treasureCatalog ) {

this.treasureCatalog[ name ].name = name;

}


//---■戦闘思考ルーチン

this.tacticz = {

slime : function( ourselves, enemies, battleLog ) {

/*

result = {

menus

argz

story

selectedItem

}

*/

return {

argz : {

enemy : enemies[ 0 ],

},

story : this.battleStories[ "たたかう" ],

}

},

}

//---■モンスター

this.monsterCatalog = {

"モモスラ" : {

image : this.artz.imagez.slime,

property : {

copy : true,

hp : 10,

atack : 5,

defence : 5,

},

tactic : this.tacticz.slime,

}

}

//tweak. キーを名前として要素内に登録

for( let name in this.monsterCatalog ) {

this.monsterCatalog[ name ].name = name;

}


//---■戦闘コマンド 実行


this.battleStories = {

"たたかう" : async function( argz ) {

this.openMessage();

this.message.clear();

await this.message.write( argz.self.name + "は " + argz.enemy.name + " にこうげき!#n" );

//check.

if( argz.enemy.property.hp == 0 ) {

await this.message.write( "しかし" + argz.enemy.name + "はすでに倒れている!#k" );

return;

}


let enemyDamage = argz.self.property.atack;

enemyDamage -= argz.enemy.property.defence;


if( enemyDamage > 0 ) {


for( let i = 0; i < 4; i++ ) {

argz.enemy.flg = ! argz.enemy.flg;

await this.delay( 100 );

}


await this.message.write( argz.enemy.name + "に " + enemyDamage + " ポイントのダメージをあたえた!#k" );

argz.enemy.property.hp -= enemyDamage;

//check.

if( argz.enemy.property.hp <= 0 ) {

argz.enemy.property.hp = 0;

await this.message.write( "#n" + argz.enemy.name + "は倒れた!#k" );

}

} else {

await this.message.write( "ミス!ダメージをあたえられない!#k" );

}

}

}


//---■戦闘コマンド

this.batcomz = {

"たたかう" : {

title : "あいては?",

items : function() {

return this.monsters.filter( m => m.property.hp > 0 );

},

argzName : "enemy",

row : 1,

story : this.battleStories[ "たたかう" ],

},

};

//tweak. キーを名前として要素内に登録

for( let key in this.batcomz ) {

this.batcomz[ key ].name = key;

}


//---■キャラクター


this.characters.push( {

name : "キャラ1",

property : {

hp : 20,

atack : 10,

defence : 10,

},

"どうぐ" : [

{ name : "つるぎ" },

this.treasureCatalog[ "なんこう" ],

Utl.copyObjectKai( this.treasureCatalog[ "ひほう" ] ),

{ name : "つるぎ" },

],

batcoms : [

this.batcomz[ "たたかう" ],

],

} );


this.characters.push( {

name : "キャラ2",

property : {

atack : 10,

defence : 10,

},

"どうぐ" : [

{ name : "なんこう2" },

{ name : "つるぎ" },

],

batcoms : [

this.batcomz[ "たたかう" ],

],

} );

this.characters.push( {

name : "キャラ3",

property : {

atack : 10,

defence : 10,

},

"どうぐ" : [

{ name : "なんこう3" },

{ name : "つるぎ" },

],

batcoms : [

this.batcomz[ "たたかう" ],

],

} );

Utl.array2object( this.characters, this.characterz, element => element.name );


}//initialize()

start() {//m

//初期化が3つに分かれているのはなぜか:

//this.start() は止めたり再開したりするためのもの。

//this.initialize() は 停止と再開を考慮しなくてもよい静的な初期化を行う。

//また、this.initialize() は非同期処理 async ができる。

//(this.constructor() は非同期処理ができない)

//そういうわけで初期化が、constructor, initialize, start と3つに分かれている。


//------------


this.focus();

this.keys = new Array();

this.keyLocked = false;


this.memoryCard = {

player : {

position : {

map : App.worldmap,

x : 0,

y : 0,

},

direction : 0,

gold : 200,

},

}


//タイトル画面から始める

let titleScreen = new TitleScreen( {

innerCellSize : this.style.gridCellSize * 2,

gridCol : 0,

gridRow : 0,

gridCols : this.style.gridCols,

gridRows : this.style.gridRows,

}, this );

this.appendChild( titleScreen );



this.startFrame();


}//start()

// |

// V

stop() {//m

}

// |

// V

startFrame() {//m

this.keys.length = 0;

this.beforeTime = 0;

this.frame( 0 );

onkeydown = this.onkeydownx.bind( this );

onkeyup = this.onkeyupx.bind( this );

}

// |

// V

stopFrame() {//m

cancelAnimationFrame( this.timerId );

this.keys.length = 0;

onkeydown = null;

onkeyup = null;

}


// |

// V

//プリロード

load( type, name, src ) {//m

return new Promise( function( promiseOk ) {

let object;

console.log( type, name, "loading.." );

switch( type ) {

case "image":

object = new Image();

this.artz.imagez[ name ] = object;

object.onload = function() {

console.log( "\t", type, name, "loaded." );

promiseOk();

}

object.src = src;

break;

case "bgm":

object = new Audio();

object.oncanplaythrough = function() {

this.artz.bgms[ name ] = object;

object.oncanplaythrough = null;

console.log( "\t", type, name, "loaded." );

promiseOk();

}.bind( this );

object.src = src;

object.load();

break;

case "sound":

object = new Audio();

object.oncanplaythrough = function() {

this.artz.soundz[ name ] = object;

object.oncanplaythrough = null;

console.log( "\t", type, name, "loaded." );

object.rapidPlay = function() {

object.pause();

object.currentTime = 0;

object.play();

/*

同じ音楽ファイルの連続演奏時は

最初にリセットをかける必要がある。

rapid=急速

*/

}

promiseOk();

}.bind( this );

object.src = src;

object.load();

break;

}

}.bind( this ) );

}//load()


onkeydownx( e ) {//m


//debug. 現在の階層構造を表示

if( e.which == 79 ) { //'o'キー

console.log( "=== 階層構造 ===" );

console.log( this.debug_dir( this ) );

console.log( "=== /階層構造 ===" );

return;

} else if( e.which == 49 ) { //'1'キー

console.log( "=== メトロノームリスト ===" );

this.debug_metronome( this );

console.log( "=== /メトロノームリスト ===" );

return;

} else if( e.which == 69 ) { //'e'キー

console.log( "=== エフェクトリスト ===" );

this.debug_effect( this.mapScreen );

console.log( "=== /エフェクトリスト ===" );

} else {

// console.log( e.which );

}


if( this.keys.indexOf( e.which ) == -1 ) {


this.keys.push( e.which ); //キーセンス オン


this.currentCraft.typeKey( e.which );

//キータイプ 実行

}

}

// |

// V

onkeyupx( e ) {//m

let idx = this.keys.indexOf( e.which );

if( idx > -1 ) this.keys.splice( idx, 1 );

}


//---汎用的メソッド(小道具)※名前にとつけると大げさになるもの

flushKey() {//m

this.keys.length = 0;

}


//---汎用的メソッド(ユーティリティ)

addMetronome( name /*other args*/ ) {//m

//オーバーロード(メソッド名が同じで引数のパターンが異なる)の模造

//”関数の先頭で引数の型を判定する条件分岐で対応”

let argsId = "";

for( let arg of Array.from( arguments ) ) {

argsId += "," + ( typeof arg ).substr( 0, 1 );

}

let metronome;

switch( argsId ) {

case ",s" : //フォーマット名称 t時間_iインデックス上限

let tokens = name.split( /_/ );

metronome = {

maxTime : Number( tokens[ 0 ].substr( 1 ) ),

maxIndex : Number( tokens[ 1 ].substr( 1 ) ),

time : 0,

index : 0,

toggle : false,

changed : false,

}

this.metronomez[ name ] = metronome;

break;

case ",s,n" : //自由な名称、時間

let tm = arguments[ 1 ];

metronome = {

maxTime : tm,

maxIndex : 0,

time : 0,

index : 0,

toggle : false,

changed : false,

}

this.metronomez[ name ] = metronome;

break;

default:

alert( "addMetronome()にて、未定義の引数リスト:[" + args + "]" );

}

return metronome;


}//addMetronome()

// |

// v

deleteMetronome( metronome ) {//m

delete this.metronomez[ metronome.name ];

}


// |

// V

frame( tm ) {//m



//経過時間

let diff = tm - this.beforeTime;

this.beforeTime = tm;


//メトロノーム更新

for( let metronome of Object.values( this.metronomez ) ) {

metronome.time += diff


//check. そのメトロノームは時を刻んだ

if( metronome.time >= metronome.maxTime ) {

metronome.time = 0;

metronome.toggle = ! metronome.toggle;

metronome.changed = true;

metronome.index ++;

//check. インデックスリセット

if( metronome.index == metronome.maxIndex ) metronome.index = 0;

} else {

metronome.changed = false;

}

}


//キーセンス 処理

for( let i = this.keys.length - 1; i >= 0; i-- ) {

let key = this.keys[ i ];

this.currentCraft.senseKey( Math.abs( key ) );

}


//1ドットスクロール実行

if( this.mapScreen )

if( this.mapScreen.scroll.isAnimating ) {

this.mapScreen.frameForScroll();

}


this.storyCheck();


this.draw( this.cc );


this.timerId = requestAnimationFrame( this.frame.bind( this ) );



}

// |

// V

async storyCheck() {//m

//ストーリー検索&実行

let stories = this.currentCraft.searchStory();

//check.

if( ! stories ) return;

for( let story of stories ) {

let res = await story.story.call( this, story.action );

//check. 真を返したら以降を打ち切り(町に入るところで戦闘発生等防ぐため)

if( res ) break;

}

}


openMessage( style ) {//m

//check. デフォルト値と上書き

style = Object.assign( {

gridCol : 2,

gridRow : 18,

gridCols : 28,

gridRows : 8,

innerCellSize : this.style.innerCellSize,

}, style );


this.closeMessage();

this.message = new MessageWindow( style, this );

this.appendChild( this.message );

}

// |

// V

closeMessage() {//m

//check.

if( ! this.message ) return;


this.removeChild( this.message );

this.message = null;

}

// |

// V


yesno( closeFlg ) {//m

this.artz.soundz.pushA.rapidPlay();


let yesnoMenu = new Menu( "selectmode", "", this.menusrcz.yesno, {

gridCol : 26,

gridRow : 25,

}, this );

this.appendChild( yesnoMenu );


return new Promise( function( promiseOk ) {

console.log( "promised 'yesno'" );

yesnoMenu.promiseOk = promiseOk;

}.bind( this ) ).then( function( result ) {

console.log( "promise 'yesno' completed" );

this.removeChild( yesnoMenu );

if( Utl.accessBy( result, "selectedItem" ) ) {

return result.selectedItem.name == "はい";

} else

return false;

}.bind( this ) );

}

// |

// V

//---●●● shop ●●●

async shop( menusrc, wordingz ) {//m

let treasureMenu = new Menu( "selectmode", "", menusrc, {

gridCol : 2,

gridRow : 2,

}, this );

this.mapScreen.appendChild( treasureMenu );


//所持金表示

let stat = new List( {

columns : [ { key : "title" }, { key : "value", align : "right" } ],

items : [

{

title : "エン",

value : function() { return this.memoryCard.player.gold; },

},

],


}, {

gridCol : treasureMenu.style.gridCols + 2,

gridRow : 0,

}, this.app );

treasureMenu.appendFloat( stat );


this.openMessage();


while( 1 ) {

//商品を選ぶ

await this.message.write( wordingz[ "何を買うんだ?" ] + "#n" );

treasureMenu.focus();

let result = await treasureMenu.select();


//check. お店から退出

if( ! result ) break;


let selectedItem = result.selectedItem;


this.message.focus(); //←見た目のため

await this.message.write( selectedItem.name + wordingz[ "だな。" ] + "#n" );

await this.message.write( selectedItem.price + "エン" + wordingz[ "だ。買うかね?" ] + "#n" );

if( await this.yesno() ) {

this.memoryCard.player.gold -= selectedItem.price;

await this.message.write( wordingz[ "まいどあり!" ] + "#n#k#n" );

}

}

await this.message.write( wordingz[ "ありがとうございました!" ] + "#k" );

this.closeMessage();

this.mapScreen.removeChild( treasureMenu );

}


encounter() {//m

this.battle();

}

// |

// V

//---●●● battle ●●●

async battle() {//m


//モンスターを用意

this.monsters = new Array();

let max = Math.floor( Math.random() * 5 ) + 1;

max = 1;

console.log( max );

for( let i = 0; i < max; i++ ) {

this.monsters[ i ] = Utl.copyObjectKai( this.monsterCatalog[ "モモスラ" ] ),

this.monsters[ i ].name += i + 1;

}

let masterOfMonsters = null;


let battleLog = new Array();


this.battleView = new BattleView( this.monsters, {

gridCol : 6,

gridRow : 6,

gridCols : 20,

gridRows : 12,

}, this );


//---開始エフェクト

this.keyLocked = true;

//フラッシュして

await this.effectz.flash( this.mapScreen, "red", Date.now() + 200 );

await this.effectz.flash( this.mapScreen, "yellow", Date.now() + 200 );

await this.effectz.flash( this.mapScreen, "red", Date.now() + 200 );

if( 1 ) {

//画面を登録

this.mapScreen.appendFloat( this.battleView );

//ローリングで画面が登場し

await new Promise( function( promiseOk ) {

let metronome = this.addMetronome( "batsta", 60 );

let step = 20;

let movl = 200;

let count = 0;

this.battleView.beforeEffects.push( function( cc ) {

let theta = Math.PI * 2 / step * count;

let alpha = 1 / step * count;

let movx = movl - movl / step * count;

cc.globalAlpha = alpha;

cc.rotate( theta );

cc.translate( this.x + movx, this.y );

//check.

if( ! metronome.changed ) {

return;

}

count ++;

//check.

if( count == step ) {

promiseOk();

this.app.deleteMetronome( metronome );

return true;

}

} );

}.bind( this ) );

} else {

//ローリングで画面が登場し

await this.effectz.battleStart( this.battleView );

//画面を登録

this.mapScreen.appendFloat( this.battleView );

}

//モンスターのベールが明かされる

for( let i = 0; i < 5; i++ ) {

for( let monster of this.battleView.monsters ) monster.flg = ! monster.flg;

await this.app.delay( 200 );

}

this.keyLocked = false;


this.openMessage();

let m = this.monsters.map( monster => monster.name ).join( "、" );

await this.message.write( m + "があらわれたぞぞ!#k" );

this.closeMessage();


let theEndOfTheBattle;

do {

let commandz = new Object();


this.openMessage( {

gridCol : 9,

gridRow : 18,

gridCols : 22,

gridRows : 8,

} );


//プレイヤー側のコマンド決定

for( let i = 0; i < this.characters.length; i++ ) {

let character = this.characters[ i ];

//check.

if( character.property.hp == 0 ) continue;


let cmdMenu = new Menu( "selectmode", "", {

name : "batcom",

title : character.name,

items : character.batcoms,

}, {

gridCol : 1,

gridRow : 18,

}, this.app );

this.appendChild( cmdMenu );


this.message.clear();

this.message.write( "#w0" + character.name + "はどうする?" );


let command = await cmdMenu.select();

cmdMenu.removeAllCrafts();

//check.

if( ! command ) {

i -= i == 0 ? 1 : 2;

continue;

}

//tweak.

command.argz.self = character;


commandz[ character.name ] = command;

}//for character

this.closeMessage();


//モンスター側のコマンド決定

if( masterOfMonsters ) {

//リーダーが思考する場合 未使用 未デバッグ

let resultz = masterOfMonsters.strategy.call( this, this.monsters, this.characters, battleLog );

Object.assign( commandz, resultz );

} else {

//それぞれが思考する場合

for( let monster of this.monsters ) {

//check.

if( monster.property.hp == 0 ) continue;

commandz[ monster.name ] = monster.tactic.call( this, this.monsters, this.characters, battleLog );

commandz[ monster.name ].argz.self = monster;

}

}


for( let name in commandz ) {

//check.

if( commandz[ name ].argz.self.property.hp == 0 ) continue;

let command = commandz[ name ];


await command.story.call( this, command.argz );


//check. 味方の生存者いない

if( ! this.characters.some( c => c.property.hp > 0 ) ) {

await this.message.write( "#n" + characters[ 0 ].name + "たちは全滅した…#k" );

theEndOfTheBattle = true;

break;

} else if( ! this.monsters.some( m => m.property.hp > 0 ) ) {

//敵の生存者いない

await this.message.write( "#nモンスターたちを倒した!#k" );

theEndOfTheBattle = true;

break;

} else {

theEndOfTheBattle = false;

}

}

this.closeMessage();


} while( ! theEndOfTheBattle );


this.mapScreen.removeChild( this.battleView );

}

async delay( ms ) {//m

await new Promise( promiseOk => setTimeout( promiseOk, ms ) );

}

async hitanykey( test ) {//m

let bak = onkeydown;

await new Promise( promiseOk => onkeydown = promiseOk ).then( function() {

console.log( test );

} );

onkeydown = bak;

}

debug_dir( object, tab, idx ) {//m

//check.

if( tab == undefined ) tab = "";

let str = tab;

if( object != this ) {

str += "→ childs[";

str += idx;

str += "] = ";

}

str += object.name;

if( object == this.currentCraft ) str += "*";

if( object.prevFocus ) str += " (prev:" + object.prevFocus.name + ") ";

str += "\n";


tab += " ";


for( let i = 0; i < object.childs.length; i++ ) {

let child = object.childs[ i ];

str += this.debug_dir( child, tab, i );

}

return str;

}

debug_metronome() {//m

let i = 0;

for( let name in this.metronomez ) {

console.log( i++, name );

}

}

_debug_dir( object, tab, idx ) {//m

//check.

if( tab == undefined ) tab = "";

let str = tab;

if( object != this ) {

str += "→ childs[";

str += idx;

str += "] = ";

}

str += object.name;

if( object == this.currentCraft ) str += "*";

if( object.prevFocus ) str += " (prev:" + object.prevFocus.name + ") ";

str += "\n";


tab += " ";


for( let i = 0; i < object.childs.length; i++ ) {

let child = object.childs[ i ];

str += this.debug_dir( child, tab, i );

}

return str;

}


debug_effect( target ) {//m

for( let i = 0; i < target.beforeEffects.length; i++ ) {

let effect = target.beforeEffects[ i ];

console.log( "beforeEffects[", i, "]", effect.id );

}

for( let i = 0; i < target.afterEffects.length; i++ ) {

let effect = target.afterEffects[ i ];

console.log( "afterEffects[", i, "]", effect.id );

}

}

}//App

//---▲▲▲ 3 App ▲▲▲



//---◆◆◆ 4 MessageWindow ◆◆◆

//もとは、試作である。app.jsへ適合中のもの。


class MessageWindow extends Craft {//c

constructor( style, app ) {

//スタイルデフォルト値と、その上書き

style = Object.assign( {

gridCols : app.style.gridCols - 2,

gridRows : app.style.gridRows - 2,

}, style );

//下記で自身のgridCols,gridRowsを参照しているので、2つに分けて上記であらかじめ定義

style = Object.assign( {

gridCol : 1,

gridRow : 1,

innerCols : style.gridCols / app.style.gridsPerInnerCell,

innerRows : style.gridRows / app.style.gridsPerInnerCell,

innerCellSize : app.style.innerCellSize,


showCursorOnlyWaitingKey : true, //文字列の末尾に来た時だけカーソルを表示する

cursorBlink : 500, //カーソルのブリンク間隔(0ならブリンクしない)

}, style );


super( style, app );


this.clear();


//現在値

this.showCursor = this.style.showCursorOnlyWaitingKey == false; //カーソル描画

this.cursorBlink_beforeTime = 0; //カーソルブリンク制御 経過時間

this.cursorBlink_toggle = true; //カーソルブリンク制御 描画トグル

}

clear() {//m

//文字表示マスの初期化

this.inner = new Array();

for( let row = 0; row < this.style.innerRows; row ++ ) {

this.inner.push( new Array() );

}

this.innerCursorCol = 0; //カーソル位置 横

this.innerCursorRow = 0; //カーソル位置 縦

}

linebreak() {//m

this.innerCursorCol = 0;


//ウィンドウの途中行なら

if( this.innerCursorRow < this.style.innerRows - 1 ) {

this.innerCursorRow ++;

} else {

//ウィンドウの最下行なら

//1行スクロール

this.inner.shift();

this.inner.push( new Array() );

console.log( "scroll" );

}

}

async write( messageString, waitTime ) {//m

//check. ウェイト未指定ならデフォルト値使用

if( waitTime == undefined ) {

this.waitTime = 100;

} else {

this.waitTime = waitTime;

}

//check. 文字列の末尾に来た時だけカーソルを表示する場合

if( this.style.showCursorOnlyWaitingKey ) {

this.showCursor = false;

}

//check. カーソルのブリンクに関する初期化

if( this.style.cursorBlink ) {

this.cursorBlink_beforeTime = Date.now();

this.cursorBlink_toggle = false;

}


//1文字ずつ表示

for( let i = 0; i < messageString.length; i++ ) {

let ch = messageString.charAt( i );

//check. エスケープシーケンス

if( ch == "#" ) {

let ch2 = messageString.charAt( i + 1 );

if( ch2 == "n" ) {

this.linebreak();

i++;

} else if( ch2 == "k" ) {

let bak = this.showCursor;


//check. 文字列の末尾に来た時だけカーソルを表示する場合

if( this.style.showCursorOnlyWaitingKey ) {

this.showCursor = true;

}

await this.hitanykey();

this.showCursor = bak;

i++;

} else if( ch2 == "w" ) {

let res = messageString.substr( i ).match( /#w(\d+)/ );

if( res ) {

this.waitTime = Number( res[ 1 ] );

i += res[ 0 ].length - 1;

}

} else {

}

continue;

}

this.inner[ this.innerCursorRow ][ this.innerCursorCol ] = ch;

this.innerCursorCol ++;

//check. 右端なら改行

if( this.innerCursorCol == this.style.innerCols ) {

this.linebreak();

}


//1文字あたりの時間待ち

if( this.waitTime > 0 ) await this.app.delay( this.waitTime );

}


}

hitanykey() {

let bak = onkeydown;

return new Promise( function( promiseOk ) {

onkeydown = promiseOk;

} ).then( function() {

onkeydown = bak;

} );

}

typeKey( key ) {

this.waitTime = 0;

}

draw( cc ) {//m

super.draw( cc );


//check. カーソルをブリンクさせる場合は

if( this.style.cursorBlink ) {

let diff = Date.now() - this.cursorBlink_beforeTime;

//check. ブリンク間隔を越えたら

if( diff >= this.style.cursorBlink ) {

this.cursorBlink_toggle = ! this.cursorBlink_toggle;

this.cursorBlink_beforeTime = Date.now();

}

}


//マトリクスを描画

cc.fillStyle = "black";

cc.font = this.style.innerCellSize + "px''";

for( let row = 0; row < this.style.innerRows; row++ ) {

let gy = row * this.style.innerCellSize;

for( let col = 0; col < this.inner[ row ].length; col++ ) {

let gx = col * this.style.innerCellSize;

cc.fillText( this.inner[ row ][ col ], gx, gy + this.style.innerCellSize );


}

}


//カーソル表示

if( this.showCursor && this.cursorBlink_toggle ) {

let gx = this.innerCursorCol * this.style.innerCellSize;

let gy = this.innerCursorRow * this.style.innerCellSize;

cc.fillText( "▼", gx, gy + this.style.innerCellSize );

}

}

}//MessageWindow



// |

// V

//---■■■ 5 List ■■■

class List extends Craft {//c

constructor( src, style, app ) {

//check. styleのデフォルト値と上書き

style = Object.assign( {

innerCellSize : app.style.gridCellSize,

gridCol : 0,

gridRow : 0,

}, style );


//リストの項目

let items;

if( src.items instanceof Function )

items = src.items.call( app ); //関数から得る

else

items = src.items;


//check. リストの列数

if( style.gridCols == undefined ) {

//各列において、その全行の文字列の中での最大文字数を得る

//その最大文字数をstyle.gridColsへ加算していき、ウィンドウの横幅とする

style.gridCols = 0;

for( let column of src.columns ) {

let strings = new Array();

for( let item of items ) {

let string = ( item[ column.key ] instanceof Function )

? item[ column.key ].call( app )

: item[ column.key ];

strings.push( string );

}

column.cols = Utl.maxLengthBinDiv2( strings ) + 1; //その列の幅

column.col = style.gridCols; //その列の位置

style.gridCols += column.cols;

}

//check. タイトルのほうが長ければ

if( src.title != undefined ) {

style.gridCols = Math.max( Utl.lengthBinDiv2( src.title ), style.gridCols );

}

//check.

if( style.gridPaddingLeft != undefined ) style.gridCols += style.gridPaddingLeft;

}

//check. リストの行数

if( style.gridRows == undefined ) {

style.gridRows = items.length;

//check.

if( src.title ) style.gridRows++;

}


super( style, app );


this.src = src;

this.items = items;

}

draw( cc ) {//m


this.drawWindow( cc );


//debug. テキストエリアに水色枠

if( 0 ) {

cc.strokeStyle = "cyan";

cc.strokeRect( 0, 0, this.w, this.h );

}


cc.fillStyle = "black";

cc.font = this.style.innerCellSize + "px ''";


//タイトル

if( this.src.title ) {

let gx = ( this.style.gridPaddingLeft - 0.3 ) * this.app.style.gridCellSize;

let gy = this.style.innerCellSize * 0.65;

cc.fillText( this.src.title, gx, gy );

}


//リスト

cc.save();

this.translateToItemArea( cc );


//行

for( let i = 0; i < this.items.length; i++ ) {

let item = this.items[ i ];

let gy = this.style.innerCellSize * i;

//列

for( let col = 0; col < this.src.columns.length; col++ ) {

let column = this.src.columns[ col ];

//値が関数になっている場合に対応 たとえば、shopの「所持金」表示

let string = ( item[ column.key ] instanceof Function )

? item[ column.key ].call( this.app )

: item[ column.key ];

let gx = column.col * this.style.innerCellSize;

//check. 右寄せ指定 たとえば数値表示

if( Utl.accessBy( column, "align" ) == "right" ) {

gx += column.cols * this.style.innerCellSize;

gx -= cc.measureText( string ).width;

}


cc.fillText( string, gx, gy + this.style.innerCellSize );

//debug. 列に緑枠

if( 0 ) {

cc.strokeStyle = "green";

cc.strokeRect(

column.col * this.style.innerCellSize,

gy,

column.cols * this.style.innerCellSize,

this.style.innerCellSize

);

}

}//for

}//for

cc.restore();

}//draw()

translateToItemArea( cc ) {//m

if( this.src.title ) cc.translate( 0, this.style.innerCellSize );

cc.translate( this.style.gridPaddingLeft * this.style.innerCellSize, 0 );

}

}//List

// |

// V

//---◆◆◆ 6 Menu ◆◆◆

class Menu extends List {//c

constructor( mode, context, src, style, app ) {

//check. 表の列設定が未定義なら1列でnameを並べるだけの通常メニュー

if( src.columns == undefined ) {

src.columns = [ { key : "name" } ];

}

//check. 左端にカーソル用の空白エリア

if( style.gridPaddingLeft == undefined ) style.gridPaddingLeft = 1;


super( src, style, app );


this.mode = mode;

this.context = context;

this.src = src;

//check. contextをthisで参照できるように

if( this.src.context ) this.context = this.src.context;


//カーソルが横にも動く場合のため(未対応中)

this.itemCols = 1; //現在使用していない。

this.itemRows = this.items.length; //縦方向は基本なのでこれは使用している


this.cursorX = 0;

this.cursorY = 0;

}

select() {//m

return new Promise(

function( promiseOk ) {

console.log( "promised 'select'" );

this.promiseOk = promiseOk;

}.bind( this )

).then(

function( result ) {

console.log( "promise 'select' completed" )

return result;

}

);

}

typeKey( key ) {//m

let addX = ( key == 39 ) - ( key == 37 );

let addY = ( key == 40 ) - ( key == 38 );


if( addX || addY ) {

this.cursorX += addX;

this.cursorY += addY;

//check.

if( this.cursorY < 0 ) this.cursorY = 0;

if( this.cursorY >= this.itemRows ) this.cursorY = this.itemRows - 1;


//カーソル 移動のタイミングで「点滅の点灯開始状態」にする

//こうしないとチラチラして見づらい

this.app.metronomez.forCursor.toggle = true;

this.app.metronomez.forCursor.time = 0;

} else {


switch( key ) {

case 32://SPACE

this.app.artz.soundz.pushA.rapidPlay();


this.selectedItem = this.items[ this.cursorY ];


//選択した項目の形態

let menusrc;

//選択した項目はメニューであるA

if( this.selectedItem.items ) {

menusrc = this.selectedItem;

} else if( this.src.assistMenu ) {

//選択した項目はメニューではないが、

//this.srcにassistMenu指定がある。B

menusrc = Utl.copyObjectKai( this.src.assistMenu );

menusrc.items = this.selectedItem[ this.src.name ];

} else if( this.selectedItem[ "assistMenu" + this.context ] ) {

//選択した項目はメニューではないが、

//this.selectedItem内にcontextMenu指定がある。C

menusrc = this.selectedItem[ "assistMenu" + this.context ];

} else {

//メニューの終了

//ストーリー内でメニューの結果を待っている場合

if( this.mode == "selectmode" ) {

//選択結果を返す

this.promiseOk( this.getResult() );

} else {

//campなどでオペレーションする場合

//埋め込まれたスクリプトをもとに実行する

this.execute();

}

return;

}


//次のメニューを表示

let col = menusrc.col == undefined

? this.selectedItem.name.length + 2

: menusrc.col;

let row = menusrc.row == undefined

? this.cursorY + 2

: menusrc.row;

let menu = new Menu( this.mode, this.context, menusrc, {

gridCol : col,

gridRow : row,

}, this.app );

this.appendChild( menu );

menu.promiseOk = this.promiseOk;


break;

case 66://B

//セレクトモードでトップメニューがキャンセルされた

let thisIsTop = ! ( this.parent instanceof Menu );

if( this.mode == "selectmode" && thisIsTop ) {

this.promiseOk( null );

} else {

//それ以外

this.parent.removeChild( this );

}

break;

}//switch

}//if else

}//typeKey()

// |

// V

getResult() {//m

let result = new Object();

result.menus = new Array();

//自分自身から上へさかのぼってmenusへ加えていく

let element = this;

while( element instanceof Menu ) {

result.menus.unshift( element );

element = element.parent;

}

result.argz = new Object();

for( let menu of result.menus ) {

if( menu.src.story ) result.story = menu.src.story; //埋め込みスクリプト

result.argz[ menu.src.argzName ] = menu.selectedItem; //そのための引数

}

result.selectedItem = result.menus[ result.menus.length - 1 ].selectedItem;


/*

メニューの選択結果は、

result.menus たどったメニュー

result.argz たどって得られた引数リスト

result.story たどりの途中で得られた埋め込みスクリプト

result.selectedItem 最後に選択した項目

なお、storyとargzは

story( argz )

のように実行する。

argzはstoryの中で、argz.char1などのように参照する。

storyはこのファイルの最初のほうのメニュー定義部分などで定義されているもの。

argz.char1のchar1などは、同じくメニュー定義部分で、argzKey : "char1"

のように定義されている。

storyの内容も、storyの中でどんな引数を使うかもユーザーが定義する。

*/

return result;

}

async execute() {//m

let result = this.getResult();


//check.

if( ! result.story ) {

alert( "storyが未定義の状態です。at menu.execute()" );

}


await result.story.call( this.app, result.argz );


//check. storyによってサブメニューの元となったモノがなくなった。

/*

たとえば、

コマンド?:どうぐ

だれの?:キャラ1

どれ?:なんこう

どうする?:つかう

だれに?:キャラ1

として、なんこうを使ったら、なんこうはなくなるので、

なんこうに由来する どうする? だれに? の2つのメニューは

消さなくてはならない。その処理↓

*/

for( let i = result.menus.length - 1; i > 0; i -- ) {

let menu = result.menus[ i ];

let selfItem = menu.parent.selectedItem;

//親メニューに自身の選択項目がない。

if( ! menu.parent.items.includes( selfItem ) ) {

menu.parent.removeChild( menu );

//メニューを1つ消せば以降のサブメニューはchildなので一緒に消える

}

}

}

draw( cc ) {//m

super.draw( cc ); //extends List


cc.save();


this.translateToItemArea( cc );


//カーソルを描画

if( this.app.currentCraft != this ||

this.app.metronomez.forCursor.toggle ) {

let w = this.style.innerCellSize * .7;

let h = this.style.innerCellSize * .8;

let gx = - this.style.innerCellSize + ( this.style.innerCellSize - w ) / 2;

let gy = this.cursorY * this.style.innerCellSize + ( this.style.innerCellSize - h ) / 2 + 1;

cc.beginPath();

cc.moveTo( gx, gy );

cc.lineTo( gx + w, gy + h / 2 );

cc.lineTo( gx, gy + h );

cc.closePath();

cc.fillStyle = "black";

cc.fill();

}


cc.restore();


super.drawChildren( cc );

}//draw()

}//Menu

//---■■■ 7 Title ■■■

class TitleScreen extends Craft {//c

constructor( style, app ) {

super( style, app );

}

async typeKey( key ) {//m

this.app.artz.soundz.pushA.play();



//TitleScreenを削除

this.app.removeChild( this );


//MapScreenを作成

this.app.mapScreen = new MapScreen( {

innerCellSize : this.app.style.gridCellSize * 2,

gridCol : 0,

gridRow : 0,

gridCols : this.app.style.gridCols + ( this.app.style.gridCols % 2 ? 0 : 1 ),

gridRows : this.app.style.gridRows + ( this.app.style.gridRows % 2 ? 0 : 1 ),

//偶数なら奇数にする

}, this.app );

this.app.appendChild( this.app.mapScreen );


//スタート時ストーリー

let map = this.app.memoryCard.player.position.map;

let x = this.app.memoryCard.player.position.x;

let y = this.app.memoryCard.player.position.y;

switch( 2 ) {

case 0: this.app.mapScreen.playerMapMoveTo( map, x, y ); //memorycard

break;

case 1: this.app.mapScreen.playerMapMoveTo( map, 3, 8 ); //外

break;

case 2: this.app.mapScreen.playerMapMoveTo( App.townmap, 3, 5 );//町

break;

}


//雨を降らせる

this.app.effectz.startRain( this.app.mapScreen, 10, 1, 3 );



this.app.openMessage();

await this.app.message.write( "スタート#k" );

this.app.closeMessage();

}

draw( cc ) {//m

cc.fillStyle = "black";

cc.fillRect( 0, 0, this.w, this.h );

cc.font = this.style.innerCellSize + "px''";

cc.fillStyle = "white";

cc.fillText( "hit any key", this.w * .35, this.h * .75 );

}

}//TitleScreen

// |

// V

//---◆◆◆ 8 Map ◆◆◆

class MapScreen extends Craft {//c

constructor( style, app ) {

// style = Object.assign( {

// gridCols : app.style.gridCols,

// gridRows : app.style.gridRows,

// }, style );

style = Object.assign( {

innerCols : style.gridCols / app.style.gridsPerInnerCell,

innerRows : style.gridRows / app.style.gridsPerInnerCell,

}, style );


super( style, app );


this.actions = new Array();


this.scroll = {

count : 0,

isAnimating : false,

tweakGx : 0,

tweakGy : 0,

}

}

assignStyle( newStyle ) {

super.assignStyle( newStyle );


this.style.innerColsHalf = Math.floor( this.style.innerCols / 2 );

this.style.innerRowsHalf = Math.floor( this.style.innerRows / 2 );

}

// |

// V

tweakColForLooping( targetCol ) {//m

//tweak. ループ時は範囲外の値を範囲内に調整する

if( this.cfg.maploop ) {

if( targetCol < 0 )

targetCol += this.mapCols;

else if( targetCol >= this.mapCols )

targetCol -= this.mapCols;

}

return targetCol;

}

tweakRowForLooping( targetRow ) {//m

//tweak. ループ時は範囲外の値を範囲内に調整する

if( this.cfg.maploop ) {

if( targetRow < 0 )

targetRow += this.mapRows;

else if( targetRow >= this.mapRows )

targetRow -= this.mapRows;

}

return targetRow;

}

getPositionAwayBy( step ) {//m

let dir = this.app.memoryCard.player.direction;

let x = this.playerCol + ( ( dir == 2 ) - ( dir == 0 ) ) * step;

let y = this.playerRow + ( ( dir == 3 ) - ( dir == 1 ) ) * step;

return { x : x, y : y }

}

// |

// V

async typeKey( key ) {//m


//check. キーを受け付けないとき

if( this.app.keyLocked || this.scroll.isAnimating ) return;


if( key == 32 ) {

//check. 向いている方向に何かがある

let pos = this.getPositionAwayBy( 1 );

let bit;

if( bit = Utl.accessBy( this.mapBits, pos.y, pos.x ) ) {

if( bit == "□" ) {

pos = this.getPositionAwayBy( 2 );

this.actions.push( { x : pos.x, y : pos.y, type : "contact" } );

return;

}

}

if( bit = Utl.accessBy( this.storyBits, pos.y, pos.x ) ) {

if( bit.sprite ) {

this.actions.push( { x : pos.x, y : pos.y, type : "contact" } );

return;

}

}


//コマンドメニューを開く

this.app.artz.soundz.pushA.rapidPlay();

let campMenu = new Menu( "executemode", "Camp", this.app.menusrcz.camp, {

gridCol : 1,

gridRow : 1,

}, this.app );

this.app.appendChild( campMenu );

campMenu.focus();

}

}

// |

// V

senseKey( key ) {//m


//check. キーを受け付けないとき

if( this.app.keyLocked || this.scroll.isAnimating ) return;


let addX, addY;


switch( key ) {

case 37: this.app.memoryCard.player.direction = 0; addX = -1; addY = 0; break;

case 38: this.app.memoryCard.player.direction = 1; addX = 0; addY = -1; break;

case 39: this.app.memoryCard.player.direction = 2; addX = 1; addY = 0; break;

case 40: this.app.memoryCard.player.direction = 3; addX = 0; addY = 1; break;

default: return;

}


//check. 移動先について確認

let destinCol = this.tweakColForLooping( this.playerCol + addX );

let destinRow = this.tweakRowForLooping( this.playerRow + addY );

//移動先は壁

let bit = Utl.accessBy( this.mapBits, destinRow, destinCol );

if( bit != null && this.cfg.walls.indexOf( bit ) > -1 ) {

this.app.artz.soundz.pushWall.play();

return;

}

//移動先はスプライト

let sprite = Utl.accessBy( this.storyBits, destinRow, destinCol, "sprite" );

if( sprite ) return;


//1マススクロールの起動

this.scroll.addX = addX;

this.scroll.addY = addY;

this.scroll.count = 0;

this.scroll.isAnimating = true;


}//senseKey

// |

// V

frameForScroll() {//m

//1マススクロールにおける、1ドット分のスクロール処理


this.scroll.tweakGx -= this.scroll.addX;

this.scroll.tweakGy -= this.scroll.addY;

//tweakGx,Gyが1ドットスクロールの主要のしくみ

//それについてはthis.draw()を参照


this.scroll.count ++;

//check. スクロールの終了

if( this.scroll.count == this.style.innerCellSize ) {

this.scroll.isAnimating = false;


this.scroll.tweakGx = 0;

this.scroll.tweakGy = 0;

//draw()はスクロール終了後も参照するからここで0にする。


this.playerCol = this.tweakColForLooping( this.playerCol + this.scroll.addX );

this.playerRow = this.tweakRowForLooping( this.playerRow + this.scroll.addY );


//マップ外に乗ったストーリー発生

if( ! this.cfg.maploop && (

this.playerCol < 0 || this.playerCol >= this.mapCols ||

this.playerRow < 0 || this.playerRow >= this.mapRows ) ) {

this.actions.push( { type : "exit" } );

} else {

//座標に乗ったストーリー発生

this.actions.push( { x : this.playerCol, y : this.playerRow, type : "walk" } );

}

}//if スクロール終了

}//frameForScroll()

// |

// V

playerMapMoveTo( mapdatafunc /*other args*/ ) {//m


//マップ間移動


//check. 

if( this.app.bgm ) {

this.app.bgm.pause();

this.app.bgm.currentTime = 0;

}


//データを記述した関数からデータを取り出す1

let commentData = mapdatafunc.toString().match( /\/\*\r\n([\s\S]+)\r\n\*\// )[ 1 ];


//データを記述した関数からデータを取り出す2

this.cfg = mapdatafunc();



//取り出したデータを使えるように加工

this.mapBits = new Array();

this.storyBits = new Array(); //位置に設定されたストーリー


//マップデータを読み取る ここから

let shortcutPositionz = new Object();

let lines = commentData.split( /\r\n/ );


for( let y = 0; y < lines.length; y++ ) {

let line = lines[ y ];


this.mapBits[ y ] = new Array();

for( let x = 0; x < line.length; x++ ) {

let mapBit = line.substr( x, 1 );


//check. 半角文字のときはストーリーへのショートカット

if( mapBit.match( /[0-9a-z ]/i ) ) {

let shortcutId = mapBit + line.substr( x + 1, 1 );

shortcutId = shortcutId.replace( / /, "" );


//check. ショートカットがリンク切れ

if( ! this.cfg.shortcuts[ shortcutId ] ) {

alert( "マップ上に配置したショートカットがリンク切れ id:" + shortcutId );

continue;

}


//マップ上のショートカットについては後で処理

shortcutPositionz[ shortcutId ] = {

x : x,

y : y,

};

//ショートカットをマップデータに置き換え

mapBit = this.cfg.shortcuts[ shortcutId ].bit;


x++; //1文字多く読んだから

}


this.mapBits[ y ].push( mapBit );

}

}//for


this.mapCols = this.mapBits[ 0 ].length;

this.mapRows = this.mapBits.length;


//storyBits初期化

for( let y = 0; y < this.mapRows; y++ ) {

this.storyBits[ y ] = new Array();

for( let x = 0; x < this.mapCols; x++ ) {

this.storyBits[ y ][ x ] = null;

}

}

//storyBitsにstoryを配置

for( let shortcutId in shortcutPositionz ) {

let pos =shortcutPositionz[ shortcutId ];

let shortcut = this.cfg.shortcuts[ shortcutId ];

this.storyBits[ pos.y ][ pos.x ] = shortcut;


//tweak. 座標情報をセット

shortcut.x = pos.x;

shortcut.y = pos.y;

}



//マップデータの端を超えるとマップの反対側になる計算

if( this.cfg.maploop ) {

this.patchwork = function( position, size ) { return ( position + size ) % size }

} else {

//町などループしない場合

this.patchwork = function( position ) { return position };

}



//マップデータを読み取る ここまで



//BGM

if( this.cfg.bgmTitle ) {

this.app.bgm = this.app.artz.bgms[ this.cfg.bgmTitle ];

this.app.bgm.loop = true;

this.app.bgm.play();

}


//メソッドの引数によって処理を分ける(オーバーロードみたいに)

let argtypes;

let argArray = Array.from( arguments );

argtypes = argArray.map( arg => ( typeof arg ).substr( 0, 1 ) ).join( "" );


//ショートカットを指定して移動

if( argtypes.indexOf( "fs" ) == 0 ) {


//playerMapMoveTo( function, string [,number] )

let shortcut = arguments[ 1 ];

this.playerCol = this.cfg.shortcuts[ shortcut ].x;

this.playerRow = this.cfg.shortcuts[ shortcut ].y;

//check.

if( typeof arguments[ 2 ] !== "undefined" )

this.app.memoryCard.player.direction = arguments[ 2 ];


} else if( argtypes.indexOf( "fnn" ) == 0 ) {


//移動先位置の座標指定して移動

//playerMapMoveTo( function, number, number [,number] )

this.playerCol = arguments[ 1 ];

this.playerRow = arguments[ 2 ];

//check.

if( typeof arguments[ 3 ] !== "undefined" )

this.app.memoryCard.player.direction = arguments[ 3 ];

} else {


alert( "playerMapMoveTo()の引数リストが想定外 : " + argtypes );


}


}//playerMapMoveTo()


searchStory() {//m

let hits = new Array();


/* 優先順位

処理順 1. 2. 3.

action walk>walk  >walk

x,y 全体>ある位置>全体

例 沼地>ジャンプ>エンカウント


つまり、一歩進んでwalkアクションになると、3種類のストーリーが起こりうる。

たとえば、沼地に入ったら沼地のダメージのストーリーがまず行われ、

つづいて、マップジャンプがそこに設定されていたらジャンプのストーリーが行われる。

最後にエンカウントのストーリーが入るが、ジャンプのストーリーは

打ち切りのフラグをreturnするので、エンカウントは行われない。

優先順位を考慮しないと、マップジャンプしたあとに突然戦闘になるとか、

沼地に踏み込んだのに、ダメージが減らないままマップジャンプするとか

になってしまう。

*/


for( let action of this.actions ) {

let story;


//1. マップチップのストーリー検索

let bit;

if( action.y != undefined && action.x != undefined )

bit = this.mapBits[ action.y ][ action.x ];

else

bit = this.cfg.outside;

if( this.cfg.mapBitStoriez[ bit ] )

if( story = this.cfg.mapBitStoriez[ bit ][ action.type ] ) {

hits.push( { action : action, story : story } );

}


//2. マップ上の位置に設定されたストーリー

if( story = Utl.accessBy( this.storyBits, action.y, action.x, "story", action.type ) ) {

hits.push( { action : action, story : story } );

}


//3. マップ自体に設定されたストーリー

if( story = this.cfg.story[ action.type ] ) {

hits.push( { action : action, story : story } );

}

}


this.actions.length = 0;

return hits;

}


draw( cc ) {//m

cc.fillStyle = this.style.backgroundColor;

cc.fillRect( 0, 0, this.w, this.h );


this.effectDraw1( cc );



let image;


cc.save();

cc.translate(

-this.style.innerCellSize / 2 + this.scroll.tweakGx,

-this.style.innerCellSize / 2 + this.scroll.tweakGy

);

//tweakX,Yが描画位置を1ドットずつずらすので

//きれいに1ドットスクロールしてるようにみえる


cc.font = this.style.innerCellSize + "px ''";

cc.fillStyle = "rgb(0,255,0)";


//this.playerCol,this.playerRowが画面中央に来るように描画開始座標を調整

let sx = this.playerCol - this.style.innerColsHalf;

let sy = this.playerRow - this.style.innerRowsHalf;


for( let inner_row = 0; inner_row < this.style.innerRows; inner_row++ ) {

let map_row = this.patchwork( sy + inner_row, this.mapRows );//両端ループ処理

let gy = inner_row * this.style.innerCellSize;

for( let inner_col = 0; inner_col < this.style.innerCols; inner_col++ ) {

let map_col = this.patchwork( sx + inner_col, this.mapCols );//両端ループ処理

let gx = inner_col * this.style.innerCellSize;


//マップを描画

let bit = Utl.accessBy( this.mapBits, map_row, map_col );

//check.

if( bit == undefined ) bit = this.cfg.outside;

image = this.cfg.images[ bit ] ? this.cfg.images[ bit ] : bit;

cc.fillText( image, gx, gy + this.style.innerCellSize );


//スプライトを描画

let story = Utl.accessBy( this.storyBits, map_row, map_col );

//check.

if( ! story ) continue;

if( ! story.sprite ) continue;

image = story.sprite;

cc.fillText( image, gx, gy + this.style.innerCellSize );

}

}



//debug. グリッド

if( 0 ) {

cc.strokeStyle = "rgba(255,255,255,.125)";

for( let row = 0; row < this.style.innerRows; row ++ ) {

for( let col = 0; col < this.style.innerCols; col ++ ) {

let x = col * this.style.innerCellSize;

let y = row * this.style.innerCellSize;

cc.strokeRect( x, y, this.style.innerCellSize, this.style.innerCellSize );

}

}

}


cc.restore();


//debug. 画面の十字線

if( 0 ) {

cc.strokeStyle = "red";

cc.beginPath();

cc.moveTo( 0, cc.canvasHeight / 2 );

cc.lineTo( cc.canvasWidth, cc.canvasHeight / 2 );

cc.stroke();

cc.beginPath();

cc.moveTo( cc.canvasWidth / 2, 0 );

cc.lineTo( cc.canvasWidth / 2, cc.canvasHeight );

cc.stroke();

}


//プレイヤーキャラ描画

cc.fillStyle = "cyan";

cc.font = this.style.innerCellSize + "px ''";

let gx = ( cc.canvasWidth - this.style.innerCellSize ) / 2;

let gy = ( cc.canvasHeight - this.style.innerCellSize ) / 2 + this.style.innerCellSize;

let sprite = this.app.artz.spritez.player;

let idx = this.app.memoryCard.player.direction * 2 + sprite.metronome.index;

image = sprite.images[ idx ];

cc.fillText( image, gx, gy );

//debug. 座標表示

if( 0 ) {

cc.fillStyle = "red";

cc.fillText( this.playerCol + "," + this.playerRow, gx, gy );

}


this.effectDraw2( cc );


this.drawChildren( cc );


}//draw()

}//MapScreen


//---■■■ 9 BattleView ■■■

class BattleView extends Craft {//c

constructor( monsters, style, app ) {

super( style, app );

this.monsters = monsters;

}

draw( cc ) {//m

this.effectDraw1( cc );

cc.drawImage( this.app.artz.imagez.battleBG, 0, 0, this.w, this.h );

if( 0 ) {

let step = this.w / ( this.monsters.length );

for( let i = 0; i < this.monsters.length; i++ ) {

let monster = this.monsters[ i ];

//check. 倒れたモンスター

if( monster.property.hp == 0 ) continue;

let zm = 0.1;

let mw = monster.image.width * zm;

let mh = monster.image.height * zm;

let gx = i * step + this.w / this.monsters.length / 2 - mw / 2;

let gy = this.h * 0.9 - mh;

cc.drawImage( monster.image, gx, gy, mw, mh );


}

} else {

let step = this.w / ( this.monsters.length + 1 );

for( let i = 0; i < this.monsters.length; i++ ) {

let monster = this.monsters[ i ];

//check. 倒れたモンスター

if( monster.property.hp == 0 ) continue;

let zm = 0.1;

let mw = monster.image.width * zm;

let mh = monster.image.height * zm;

let gx = ( i + 1 ) * step - mw / 2;

let gy = this.h * 0.9 - mh;

if( monster.flg ) {

cc.drawImage( monster.image, gx, gy, mw, mh );

} else {

cc.save();

//裏画面でシルエット画像を作成

let bcc = document.createElement( "canvas" ).getContext( "2d" );

bcc.canvas.width = monster.image.width;

bcc.canvas.height = monster.image.height;

bcc.drawImage( monster.image, 0, 0 );

bcc.globalCompositeOperation = "source-in";

bcc.fillStyle = "black";

bcc.fillRect( 0, 0, monster.image.width, monster.image.height );

//それを描画

cc.drawImage( bcc.canvas, gx, gy, mw, mh );

cc.restore();

}

}

}//if

this.effectDraw2( cc );

}//draw()

}//BattleView


//---■■■ 10 Effectz ■■■

class Effectz {//c

constructor( app ) {

this.app = app;

}


async zoom( target, metronomeMaxMs, fromScale, toScale, endTimeMs ) {

return new Promise( function( promiseOk ) {

let dir = fromScale < toScale ? 1 : -1;

let nowScale = fromScale;

let metronome = this.app.addMetronome( "forZoom", metronomeMaxMs );

target.beforeEffects.push( function( cc ) {

cc.scale( nowScale, nowScale );

//check.

if( metronome.changed ) {

//残りの動作量を残りの描画回数で割る。それをstepとする。

//これは毎回計算しなおす。(ズレを吸収する)

let remainingMotion = toScale - nowScale; //残りの動作量

let remainingTimeMs = endTimeMs - Date.now(); //残り時間

let remainingDrawing = remainingTimeMs / metronomeMaxMs;//残り描画

let step = remainingMotion / remainingDrawing;


nowScale += step;

//check.

if( ( toScale - nowScale ) * dir <= 0 ) {

promiseOk();

this.app.deleteMetronome( metronome );

return true;

}

}


}.bind( this ) );

}.bind( this ) );

}

startRain( target, metronomeMaxMs, step, hPerW ) {//m

//hPerW 雨粒の傾き


let rainLen = 4; //雨粒の長さ

//雨粒の長さと傾きが作る三角形の

let W = rainLen / Math.sqrt( 1 + hPerW * hPerW ); //底辺

let H = W * hPerW; //高さ

//雨の傾きによる三角状の空欄をなくすための、target.w増分

let plusW = target.h / hPerW;


let rains = new Array();

for( let i = 0; i < 100; i++ ) {

rains[ i ] = {

x : Math.floor( Math.random() * target.w ),

y : Math.floor( Math.random() * target.h ),

}

}

let metronome = this.app.addMetronome( "forRain", metronomeMaxMs );

let drawFunc = function( cc ) {

cc.strokeStyle = "cyan";

cc.lineWidth = 0.5;

for( let rain of rains ) {

cc.beginPath();

cc.moveTo( rain.x, rain.y );

cc.lineTo( rain.x - W, rain.y + H );

cc.stroke();


//check.

if( ! metronome.changed ) continue;


//雨粒の位置を更新

rain.y += H * step;

//check. 画面外

if( rain.y > target.h ) rain.y = rain.y % target.h;

//rain.y = 0; としてしまうと、各雨粒の水平位置がそろってしまう。

rain.x -= W * step;

//check. 画面外

if( rain.x < 0 ) {

rain.x = Math.floor( Math.random() * target.w + plusW );

rain.y = rain.y % target.h;;

}

}

}.bind( this );

drawFunc.id = "rain";

target.afterEffects.push( drawFunc );


this.app.effectz.endRain = function() {

target.afterEffects.splice( target.afterEffects.indexOf( drawFunc ), 1 );

this.app.deleteMetronome( metronome );

}

}

//戦闘スタート時の演出

async battleStart( battleView ) {//m

this.app.stopFrame();


let cc = this.app.cc;

let step = 13;

let movl = 200

for( let i = 1; i <= step; i ++ ) {

let theta = Math.PI * 2 / step * i;

let alpha = 1 / step * i;

let movx = movl - movl / step * i;

this.app.draw( cc );

cc.save();

cc.globalAlpha = alpha;

cc.translate( battleView.x + movx, battleView.y );

cc.rotate( theta );


battleView.draw( cc );

cc.restore();


await this.app.delay( 60 );

}

this.app.startFrame();


}


async rotate( target, metronomeMaxMs, maxTheta, endTimeMs ) {//m

return new Promise( function( promiseOk ) {


let theta = 0;

let metronome = this.app.addMetronome( "forRotate", metronomeMaxMs );


target.beforeEffects.push( function( cc ) {

cc.rotate( theta );

//check.

if( metronome.changed ) {

//残りの動作量を残りの描画回数で割る。それをstepとする。

//これは毎回計算しなおす。(ズレを吸収する)

let remainingMotion = maxTheta - theta; //残りの動作量

let remainingTimeMs = endTimeMs - Date.now(); //残り時間

let remainingDrawing = remainingTimeMs / metronomeMaxMs;//残り描画

let step = remainingMotion / remainingDrawing;

theta += step;

//check.

if( theta >= maxTheta ) {

promiseOk();

this.app.deleteMetronome( metronome );

return true;

}

}


}.bind( this ) );

}.bind( this ) );

}

async quake( target, width, speed, endTimeMs ) {//m

return new Promise( function( promiseOk ) {


let metronome = this.app.addMetronome( "forQuake", speed / 2 );

let draw = function( cc ) {

let x = metronome.toggle ? 0 : width;

cc.translate( x, 0 );

//check. 終了

if( metronome.changed && Date.now() >= endTimeMs ) {

promiseOk();

this.app.deleteMetronome( metronome );

return true;

}

}.bind( this );

target.beforeEffects.push( draw );


}.bind( this ) );

}

async flash( target, color, endTimeMs ) {//m


return new Promise( function( promiseOk ) {

let metronome = this.app.addMetronome( "forFlash", 0 );

target.afterEffects.push( function( cc ) {

if( metronome.toggle ) {

cc.fillStyle = color;

cc.fillRect( 0, 0, target.w, target.h );

}

//check. 予定時刻

if( Date.now() >= endTimeMs ) {

promiseOk();

this.app.deleteMetronome( metronome );

return true;

}

}.bind( this ) );

}.bind( this ) );

}

async fade( target, metronormMaxMs, inout, color, endTimeMs ) {//m

return new Promise( function( promiseOk ) {

let from, to;

if( inout == "out" ) {

from = 0;

to = 1;

} else {

from = 1;

to = 0;

}

let nowAlpha = from;

let metronome = this.app.addMetronome( "forFade", metronormMaxMs );


let drawFunc = function( cc ) {

//メトロノームにより値を進める。

if( metronome.changed ) {

//残りの動作量を残りの描画回数で割る。それをstepとする。

//これは毎回計算しなおす。(ズレを吸収する)

let step;

let remainingMotion = to - nowAlpha; //残りの動作量

let remainingTimeMs = endTimeMs - Date.now(); //残り時間

//check. 超えた値を修正

if( remainingTimeMs < 0 ) remainingTimeMs = 0;

let remainingDrawing = remainingTimeMs / metronormMaxMs;//残り描画

step = remainingMotion / remainingDrawing;


nowAlpha += step;

//check. 超えた値を修正

if( nowAlpha > 1 ) nowAlpha = 1;

else if( nowAlpha < 0 ) nowAlpha = 0;

}


cc.globalAlpha = nowAlpha;

cc.fillStyle = color;

cc.fillRect( 0, 0, target.w, target.h );


//check. 終了値に到達

if( nowAlpha == to ) {

promiseOk();

this.app.deleteMetronome( metronome );

return true;

}


}.bind( this );

drawFunc.id = "fade-" + inout;

target.afterEffects.push( drawFunc );

}.bind( this ) );

}


//utl

stepByRemaining( value, maxValue, endTimeMs, metronome ) {//m

//残りの動作量を残りの描画回数で割る。それをstepとする。

//動くたびに毎回計算しなおす。(ズレを吸収する)


let remainingMotion = maxValue - value; //残りの動作量

let remainingTimeMs = endTimeMs - Date.now(); //残り時間

let remainingDrawing = remainingTimeMs / metronome.maxMs; //残り描画

let step = remainingMotion / remainingDrawing;

return step;

}

}//Effectz


App.worldmap = function() {/*

●〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃¥¥¥▲〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

¥山山山山山〃~~~~~~¥¥〃〃〃〃〃〃〃〃〃¥¥山山山¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

¥山山山〃〃〃〃~~~〃〃〃〃〃〃〃〃〃〃〃〃¥¥山山山¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

¥山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃¥¥山山山¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

¥¥〃〃1 〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃¥¥山山山¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃¥¥山山山¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃沼沼沼〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃¥¥¥山山山¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃沼沼沼〃〃〃〃〃山山〃〃〃〃〃〃〃¥¥¥山山山¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃沼沼沼〃〃〃山山山〃〃〃〃〃〃〃¥¥¥山山山¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃山山山山山〃〃〃〃〃〃〃〃¥¥山山山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃山山山〃〃〃〃〃〃〃〃¥〃〃¥山山山山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃〃〃〃〃〃〃¥¥¥〃〃〃〃山山山山山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃〃〃〃〃〃〃〃¥〃〃〃〃〃山山山山山山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

山山山〃〃〃~~~〃〃〃〃〃〃〃〃〃〃山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

山山〃〃~~~~¥¥〃〃〃〃〃〃〃〃山山山山〃〃〃〃〃〃〃山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

山〃〃~~~¥¥¥〃〃〃〃〃〃〃〃山山山山山山山〃〃〃〃〃山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃¥¥¥¥〃〃〃〃〃〃〃〃〃山山山山山山山山〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃¥¥¥〃〃〃〃〃〃〃山山山山山山山山山山~~〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃¥¥¥〃〃〃〃〃〃山山山山山~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃¥¥¥〃〃〃〃〃〃〃〃〃〃~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃¥¥¥¥¥¥¥〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃¥¥¥¥¥¥¥¥〃〃〃〃〃〃〃〃~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃¥¥¥¥¥¥〃〃〃〃〃〃〃〃〃~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃¥¥¥¥¥〃〃〃〃〃〃〃〃〃〃~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃¥¥〃〃〃〃〃〃〃〃〃〃~~~~~〃〃〃〃〃〃〃¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃¥¥〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

〃〃〃〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

○〃〃〃〃〃〃〃〃~~~~~~~〃〃〃〃〃〃〃〃〃〃〃〃〃△〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃山山山山

*/

return {

maploop : true,

bgmTitle : "field",

walls : "~",

shortcuts : {

"1" : {

bit : "凸",

story : {

"walk" : async function( act ) {

this.keyLocked = true;

let nowMs = Date.now();

await this.effectz.fade( this.mapScreen, 50, "out", "black", nowMs + 500 );

this.mapScreen.playerMapMoveTo( App.townmap, "s", 2 );

await this.effectz.fade( this.mapScreen, 50, "in", "black", nowMs + 1000 );

this.keyLocked = false;

return true; //他のstory実行を打ち切る意

},

},

},

},

story : {

"walk" : function( act ) {

//エンカウント

if( Math.random() > .8 ) {

this.encounter();

}

},

},

mapBitStoriez : {

"沼" : {

"walk" : async function() {

this.keyLocked = true;

this.artz.soundz.damage.rapidPlay(); //音声

await this.effectz.flash( this.mapScreen, "red", Date.now() + 100 );

await this.delay( 200 );

this.keyLocked = false;

},

},

},

images : {

"¥" : "🌲",

"凸" : "🏰",

"山" : "⛰",

"~" : "🌊",

"沼" : "☠",

},

}//return

}//worldmap



App.townmap = function() {/*

木木木木木木木〃・・〃木木木木木木木木木〃・・〃〃〃〃〃〃〃

木■■■■■木〃・・〃木■■■■■■■木〃・・〃〃〃〃〃〃〃

木■刀刀刀■木〃・・〃木■■■■■■■木〃・・〃〃〃〃〃〃〃

木■・ws・■木〃・・〃木■■■■■■■木〃・・〃〃〃〃〃〃〃

木■■□■■木〃・・〃木■■■扉■■■木〃・・〃〃〃〃〃〃〃

木木〃・剣木木〃・・〃〃花花花・花花花a2〃・・〃〃〃〃〃〃〃

s ・・・・・・・・・・・・・・・・・・・・・・・・・・・・・

・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・

木木盾・〃木木〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

木■■□■■木〃・・〃a1〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

木■・ss・■木〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

木■盾盾盾■木〃・・〃〃◇〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

木■■■■■木〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

木木木木木木木〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃沼沼〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃沼沼沼〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃沼沼沼沼〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃沼沼沼沼〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃沼沼沼・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

〃〃〃〃〃〃〃〃・・〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃〃

*/

return {

maploop : false,

bgmTitle : "town",

shortcuts : {

"s" : {

bit : "・",

},

"ws" : {

bit : "👨‍🔧",

story : {

"contact" : async function( e ) {

this.openMessage();

await this.message.write( "ここは刀剣屋だ。#k" );

this.closeMessage();

await this.shop( {

name : "shop",

title : "刀剣屋 'ロバートウッドテイル'",//全13, 半3

items : [

{ name : "どうけん", price : 100, },

{ name : "せいどうけん", price : 200, },

{ name : "てっけん", price : 300, },

{ name : "こうてつけん", price : 400, },

{ name : "いばらきけん", price : 18000, },

{ name : "ちばけん", price : 78000, },

{ name : "とっとりけん", price : 10102, },

{ name : "ふくしまけん", price : 29400, },

],

columns : [

{

key : "name",

},

{

key : "price",

align : "right",

}

],

}, {

"何を買うんだ?" : "何を買うんだ?",

"だな。" : "だな。",

"だ。買うかね?" : "だ。買うかね?",

"だ。買うかね?" : "だ。買うかね?",

"まいどあり!" : "まいどあり!",

"ありがとうございました!" : "ありがとうございました!",

} );

console.log( "shop closed." );

},

},

},

"ss" : {

bit : "👩‍🔧",

story : {

"contact" : async function( e ) {

this.openMessage();

await this.message.write( "ここは道具屋よ。#n#k" );

this.closeMessage();

await this.shop( {

name : "shop",

title : "道具屋 'セリーヌ・デ・イオン'",//全13, 半3

items : [

{ name : "なんこう", price : 100, },

{ name : "羊皮紙", price : 200, },

{ name : "黄金の羊", price : 200000, },

],

columns : [

{

key : "name",

},

{

key : "price",

align : "right",

}

],

}, {

"何を買うんだ?" : "何を買いますか?",

"だな。" : "ね。",

"だ。買うかね?" : "です。買いますか?",

"まいどあり!" : "ありがとうございます!",

"ありがとうございました!" : "ありがとうございました!",

} );

console.log( "shop closed." );

},

},

},

"a1" : {

bit : "〃",

sprite : "🕵️‍♂️",

story : {

"contact" : async function( e ) {

this.openMessage();

await this.message.write( "ドラゴンなら北のほうへ飛んで行ったぜ#k" );

this.closeMessage();

},

},

},

"a2" : {

bit : "〃",

sprite : "👩‍🚒",

story : {

"contact" : async function( e ) {

this.openMessage();

await this.message.write( "あなたのつるぎの使い方、\nふつうじゃないよね?#k" );

this.closeMessage();

},

},

},

},

story : {

"exit" : async function( e ) {

this.keyLocked = true;

this.artz.soundz.damage.rapidPlay(); //音声

let nowMs = Date.now();

await this.effectz.fade( this.mapScreen, 50, "out", "black", nowMs + 500 );

this.mapScreen.playerMapMoveTo( App.worldmap, "1", 3 );

await this.effectz.fade( this.mapScreen, 50, "in", "black", nowMs + 1000 );

this.keyLocked = false;

},

},


outside : "〃",

walls : "■□扉",

mapBitStoriez : {

"◇" : {

"walk" : function() {

this.effectz.quake( this.mapScreen, 3, 100, Date.now() + 1000 );

},

},

"沼" : {

"walk" : async function() {

this.keyLocked = true;

await this.effectz.flash( this.mapScreen, "red", Date.now() + 100 );

await this.delay( 100 );

this.keyLocked = false;

},

"exit" : function() {

this.effect_flash( "darkred" );

},

},

},

images : {

"木" : "🌳",

"花" : "🌷",

"扉" : "🚪",

"箱" : "📦",

"座" : "🪑",

"寝" : "🛏",

"∪" : "🏺",

"剣" : "⚔",

"刀" : "🗡",

"盾" : "🛡",

"沼" : "☠",

},

}//return

}//townmap




(訪問者のどんなニーズと この記事がつながるか)

  • 日記を読みたい